react倒计时组件

回到文章
<div id="app"></div>
.item {
        display: flex;
    }
const { Component, useState } = React;

class CountDown extends Component {
  timer = null;

  state = {
    status: "pending",
    endTime: 0,
    progress: 0,
  };

  // 开始倒计时
  start() {
    this.execute("onStart");
    this.setState({ status: "running" });
    const { diff, format } = this.props;

    const running = () => {
      const now = Date.now();
      const progress = Math.max(this.state.endTime - now, 0);
      let _progress = "";
      if (typeof format === "string") {
        _progress = this.formatTime(progress, format);
      } else if (typeof format === "function") {
        _progress = format.call(null, progress);
      }

      this.setState({ progress: _progress });
      this.execute("onStep", _progress);

      if (progress === 0) {
        this.stop();
      }
      return progress;
    };
    if (diff && diff >= 17) {
      this.timer = setInterval(() => {
        running();
      }, diff);
    } else {
      const requestAnimFrame =
        window.requestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        function (callback) {
          window.setTimeout(callback, 1000 / 60);
        };

      setTimeout(() => {
        (function loop() {
          const progress = running();
          if (progress > 0) {
            requestAnimFrame(loop);
          }
        })();
      }, 0);
    }
  }

  // 停止倒计时
  stop() {
    clearInterval(this.timer);
    this.setState({ status: "ended" });
    this.execute("onEnd");
  }

  componentDidMount() {
    const { endTime, total } = this.props;

    if (!endTime && !total) {
      // 至少需要一个参数
      console.error(`endTime and total need least one`);
    } else {
      const now = Date.now();
      let _endTime = 0;
      if (!endTime) {
        _endTime = now + (total || 0);
      } else {
        _endTime = endTime;
      }

      this.setState({ endTime: _endTime });
      this.start();
    }
  }

  formatTime(timestamp, format = "dd hh:mm:ss.ii") {
    const dateFormat = {
      "d+": Math.floor(timestamp / 1000 / 60 / 60 / 24), // 天
      "h+": Math.floor((timestamp / 1000 / 60 / 60) % 24), // 时
      "m+": Math.floor((timestamp / 1000 / 60) % 60), // 分
      "s+": Math.floor((timestamp / 1000) % 60), // 秒
      "i+": timestamp % 1000, // 毫秒
    };

    for (let key in dateFormat) {
      if (new RegExp("(" + key + ")").test(format)) {
        format = format.replace(RegExp.$1, () => {
          const regLen = RegExp.$1.length;
          if (/i+/.test(RegExp.$1)) {
            return (dateFormat[key] + "000").substr(0, regLen);
          }
          return regLen === 1 ? dateFormat[key] : ("00" + dateFormat[key]).substr(("" + dateFormat[key]).length);
        });
      }
    }
    return format;
  }

  // 触发props中传过来的回调
  execute(fn, ...params) {
    const func = this.props[fn];
    if (typeof func === "function") {
      func(...params);
    }
  }

  render() {
    return <p>{this.state.progress}</p>;
  }
}
const SelfFormatCount = () => {
  const [ended, setEnded] = useState(false); // 倒计时是否结束的标识
  return (
    <div>
      <CountDown
        total={5000}
        format={(timestamp) => {
          return (timestamp / 1000).toFixed(3);
        }}
        onEnd={() => setEnded(true)}
      />
      {ended && <p style={{ color: "#f00" }}>已结束</p>}
    </div>
  );
};
ReactDOM.render(
  <div>
    <div class="item">
      <p>10秒钟的倒计时:</p>
      <CountDown total={10000} format={"mm:ss.iii"} />
    </div>
    <div class="item">
      <p>自定义格式的10秒钟的倒计时:</p>
      <SelfFormatCount />
    </div>
    <div class="item">
      <p>距离早晨8点的倒计时:</p>
      <CountDown endTime={new Date().setHours(8, 0, 0, 0)} format={"hh小时mm分ss秒"} />
    </div>
    <div class="item">
      <p>距离中午12点30分的倒计时:</p>
      <CountDown endTime={new Date().setHours(12, 30, 0, 0)} format={"hh小时mm分ss秒"} />
    </div>
    <div class="item">
      <p>距离今晚24点的倒计时:</p>
      <CountDown endTime={new Date().setHours(24, 0, 0, 0)} format={"hh小时mm分ss秒"} />
    </div>
  </div>,
  document.getElementById("app")
);