一、描述

rax-cutdown 是 rax 内置的倒计时组件,对于需要使用倒计时场景的页面来说,这个组件封装的十分方便,可配置性高,包括可以设置毫秒倒计时以及倒计时的模板和样式等,比较灵活。

官方文档:

支持的 props 非常多,不在这里重复,可以去查看官方的文档。

二、源码

rax-cutdown 是 rax 自己实现的组件,因此使用的依赖也都是 rax 的依赖,没有直接使用 weex 的内容。

主要使用的 rax 组件如下:

  • import Text from 'rax-text';
  • import View from 'rax-view';
  • import Image from 'rax-image';

1、支持的 props

rax-cutdown 组件支持如下的 props,描述写在了下面代码的注释中:

static propTypes = {
  formatFunc: PropTypes.func,  // 自定义日期格式方法,如果不是 undefined,tpl会失效
  onTick: PropTypes.func,  // 倒计时变化时调用的方法
  onComplete: PropTypes.func, // 倒计时完成时调用的方法
  tpl: PropTypes.string, // template (example {h}:{m}:{s}) // 倒计时展示模板,默认为'{d}天{h}时{m}分{s}秒'
  timeRemaining: PropTypes.number, // 倒计时的时间 
  secondStyle: PropTypes.object,  // 秒最后一位样式
  timeStyle: PropTypes.object, // 时间-数字的样式
  textStyle: PropTypes.object, // 时间-单位的样式
  timeWrapStyle: PropTypes.object, // 各时间区块的样式
  timeBackground: PropTypes.string, // 各时间区块背景(可加背景图)
  timeBackgroundStyle: PropTypes.object, // 各时间区块背景样式
  interval: PropTypes.number // 倒计时的间隔,单位为"毫秒"
};

默认的 props 定义如下:

static defaultProps = {
  tpl: '{d}天{h}时{m}分{s}秒',
  timeRemaining: 0,
  interval: 1000
};

2、tick() 触发的时机

tick 是每次时间变动后触发的方法,因此在 componentDidMountcomponentDidUpdate 两个生命周期中触发 tick 方法。

 componentDidMount() {
    this.tick();
  }

  componentDidUpdate() {
    this.tick();
  }

3、更新时机

触发更新的时候,如果两次的倒计时时间不一样,则会清楚当前的 timerId,并且重置 state

componentWillReceiveProps(newProps) {
  if (newProps.timeRemaining !== this.props.timeRemaining) {
    if (this.timeoutId) {
      clearTimeout(this.timeoutId);
    }
    this.setState({
      timeRemaining: newProps.timeRemaining
    });
  }
}

4、更新机制

为了避免 rax-cutdown 的 state 的更新,只有当 props 更新之后,才会进行组件的重新渲染

  shouldComponentUpdate(nextProps, nextState) {
    return this.props.timeRemaining !== nextProps.timeRemaining ||
      this.state.timeRemaining !== nextState.timeRemaining;
  }

5、核心的 tick() 方法

上面提到了 tick() 方法的触发时机,挂载完或者更新的时候会触发一次,tick() 组件完成的工作内容如下:

tick = () => {
  const {onComplete, onTick, interval} = this.props;
  const {timeRemaining} = this.state;
  const countdownComplete = 1000 > timeRemaining;

  if (this.timeoutId) {
    clearTimeout(this.timeoutId);
  }

  if (countdownComplete && isFunction(onComplete)) {
    onComplete();
  } else {
    this.timeoutId = !countdownComplete ? setTimeout(
      () => this.setState(
        {
          timeRemaining: timeRemaining - interval
        },
        () => isFunction(onTick) && onTick(timeRemaining)),
      interval
    ) : false;
  }
};

首先每次触发都会执行 timeRemaining = timeRemaining - interval,因此每次都会判断 1000 > timeRemaining 如果成立了,则直接执行 onComplete 逻辑。

同样需要注意的是,rax-cutdown 是利用 setTimeout 实现的,而不是使用 setInterval 实现的,rax-cutdown 的机制就是,再触发 tick() 的时候,首先会清楚上次的 timeoutId,然后在开一个新的 timeoutId,这样子可控性非常高。

三、使用

import {createElement, Component} from 'rax';
import View from 'rax-view';
import Text from 'rax-text';
import Countdown from 'rax-countdown';
import styles from './App.css';

class App extends Component {
  render() {
    return (
      <View style={styles.app}>
        <Countdown
            timeRemaining={100000000}
            tpl={'{d}天{h}时{m}分{s}秒'}
            onComplete={this.onComplete}
          />
      </View>
    );
  }
}

export default App;

GwIF.gif