一、描述

universal-toast 是 rax 内置的提示反馈组件,貌似在淘宝的页面中使用的很多,虽然我觉得他的动画有点怪怪的。

所有的 universal-*rax-* 是不一样的,因为 universal-* 更多的相当于一个包,而不是带模板组件。

从源代码中也可以看出来,所有的 universal-* 是放在 package 中的,仓库地址:

如果只是实用 universal-toast 则可以直接参考官方文档,使用方式很简单,在 native 环境,还是基于 weex 的内置模块去实现的,主要看一下他的主要实现过程。

二、源码

1、weex 环境

如果是在 weex 环境,universal-toast 是基于 weex 的内置 module modal 实现的,本身 modal 拥有 toast 的方法 modal.toast

Toast 暴露出来的 show 方法如许埃,其中包含了 weex 环境下处理和 web 的处理(web 下面说):

show(message, duration = SHORT_DELAY) {
  if (isWeex) {
  let weexModal = __weex_require__('@weex-module/modal');
  if (weexModal.toast) {
    weexModal.toast({
      message,
      duration: Number(duration) / 1000,
    });
  }
  } else {
    toast.push(message, duration);
  }
},

本身 universal-toast 维护了两个静态变量分别是:

  • const LONG_DELAY = 3500; // 3.5 seconds
  • const SHORT_DELAY = 2000; // 2 seconds

Toast 在导出的时候将两个静态变量一起导出了,因此在使用的时候,可以直接使用 Toast.SHORT_DELAY 或者 Toast.LONG_DELAY

这两者在 weex 环境上表现是不同的,这是由 weex 本身决定的,在 Android 平台上,如果传入的 duration 参数时间长度 > 1s ,则直接使用 LONG_DELAY,如果小于 1s 则使用 SHORT_DELAY,而在 IOS 平台时间与 duration 传入的时间相同。

更多内容可以看 weex 的文档:

同样的,由于 weex 的 modal.toast duration 参数是以 秒/s 为单位的,所以 rax 内部处理了一下(见上面代码)。

2、web 容器环境

上面代码中,web 环境下只有简单的一行代码:

toast.push(message, duration);

这一行代码看起来什么用处也没有,只是透露出了一个信息,那就是 push,由此可以发现,web 容器下,rax 自己实现的 toast 是支持队列的,而 weex 内部的 module 也是支持队列。

不是很确定 rax 的这种实现机制是否和 weex 是一样的,因为还没去研究过 weex 的源代码(这是个浩大的工程),可以猜测一二。

因为存在 toast 队列,所以需要从队列中取值、销值操作,Toast 并是每次都构造一个新的 Toast DOM 内容,也就是并不是每次都构造一个 toastWin,而是只构造一个,用来承载所有的 toast 内容的显示,当队列为空的时候,则销毁 toastWin

toast 对象(就是上面那一行代码中使用的 toast.push )包含两个方法,分别是 pushshow,而 show 方法是在 push 方法中调用。

toast 对象的代码定义如下:

let toast = {
  push(message, duration) {
    queue.push({
      message,
      duration
    });
    this.show();
  },

  show() {
    // All messages had been toasted already, so remove the toast window,
    if (!queue.length) {
      if (toastWin) {
        toastWin.parentNode.removeChild(toastWin);
      }
      toastWin = null;
      return;
    }

    // the previous toast is not ended yet.
    if (isProcessing) {
      return;
    }
    isProcessing = true;

    let toastInfo = queue.shift();
    showToastWindow(toastInfo.message);
    setTimeout(() => {
      hideToastWindow();
      isProcessing = false;
      setTimeout(() => this.show(), 600);
    }, toastInfo.duration);
  }
};

push() 方法比较好理解,无非就是队列 push 值,再调用一次 show() 方法。

show() 方法中,主要的处理过程如下:

  • 判断队列是否为空,如果队列为空,则将 toastWin 容器销毁
  • 判断是否正在进行 toast 显示,如果是,则直接跳出本次判断
  • 从队列中取出(queue.shift())值,调用 showToastWindow()方法显示容器
  • 设定定时器,在 duration 到了之后,执行下列操作:

    • hideToastWindow() 方法隐藏容器
    • isProcessingfalse
    • 再次执行 this.show() 这里还是使用 setTimeout 进行 hack,保证结束后再次 show()

至于 showToastWindow(message)hideToastWindow() 两个方法,则很简单,无非是往 ToastWin 上挂载 和销毁 DOM 节点,需要注意的是,ToastWin 只有第一次不存在的时候才回去创建,如果存在了则不会去创建,而且每次 hideToastWindow() 并不是直接将 ToastWin 销毁,而只是将其进行 transform 掉,只有队列为空的时候才销毁,队列不为空,只是替换 innerHTML而已,代码方面的流程可以看 universal-toast 的源代码。

三、使用

开头说了,如果只是使用的话,还是很简单的, 比如下面的代码:

componentWillMount = () => {
  Toast.show('toast1');
  Toast.show('toast2');
  Toast.show('toast3');
}
componentDidMount = () => {
  Toast.show('toast11');
  Toast.show('toast22');
  Toast.show('toast33');
}

效果如下:

GIF.gif