3
场景重现

之前在做一个IM的模块,有一个撤回消息后两分钟之内可以重新编辑的功能,项目是用react做的,按照正常的思路,只需传入一个消息撤回的时间markTime,然后用现在时间Date.now()去判断是否已经超时两分钟。然而理想总是美好的,由于没有去触发react的重新渲染机制,即使超时了重新编辑的按钮也不会消失,等到去点击重新编辑按钮再提示已超时的话这个极大的影响用户体验。

解决方案
  • 思路I

可以给每条撤回的系统消息加上一个setInterval定时器轮询,只要未超时就实时更新当前时间,直至超时再把这个定时器清除掉;
但是这个方法有个弊端,就是假设我在有限时间内撤回了非常多条消息,那么这非常多条的消息就会有着对应的多个定时器在工作着,这对于性能的损耗特别的大,对于本公司的项目,团队在性能优化上还是有追求的,所以思路I被否决掉了;

  • 思路II

通过静态方法维护同一个定时器订阅器,如果该订阅器中还有存在的未超过有限时间的事件,则展示其中未超过有限时间的子组件,如果已超时,则不展示其对应的自组件,返回一个null
上代码

/**
 * 通过静态方法维护同一个定时器,传入标记时间markTime和在这段有效时长alidTimeLong内,2000毫秒更新一次组件,如果超过有效时长则返回null
 */
import React from "react";

type UpDateFunc = (now: number) => boolean;
// 使用该组件时,需要传入两个参数,一个开始定时的时间,一个有效时长,都为number类型
export default class TimePlay extends React.Component<{
  markTime: number;
  validTimeLong: number;
}> {
  // 维护一个Set的订阅器
  static countDownFuncList = new Set<UpDateFunc>();
  // 定时器的初始状态
  static intervalHandel = -1;
  // 往订阅器中添加一个需要监听的事件
  static addFunc = (func: UpDateFunc) => {
    TimePlay.countDownFuncList.add(func);
    TimePlay.countDown();
  };
  // 订阅器中删除一个超时的事件
  static removeFunc = (func: UpDateFunc) => {
    TimePlay.countDownFuncList.delete(func);
  };
  // 订阅器中如果还存在事件,则在同一个定时器下执行
  static countDown = () => {
    if (TimePlay.intervalHandel !== -1) {
      return;
    }
    TimePlay.intervalHandel = setInterval(() => {
      if (TimePlay.countDownFuncList.size === 0) {
        clearInterval(TimePlay.intervalHandel);
        TimePlay.intervalHandel = -1;
      }
      const now = Date.now();
      for (const fn of TimePlay.countDownFuncList) {
        const isValid = fn(now);
        if (!isValid) {
          TimePlay.removeFunc(fn);
        }
      }
    }, 2000);
  };
  // 判断是否展示子组件
  state = {
    isShowChildren: this.props.markTime + this.props.validTimeLong > Date.now(),
  };
  // 用于初始时判断是否超时
  updateState = (now: number) => {
    const isValid = this.props.markTime + this.props.validTimeLong > now;
    if (isValid && !this.state.isShowChildren) {
      this.setState({ isShowChildren: true });
    }
    if (!isValid) {
      this.setState({ isShowChildren: false });
    }
    return isValid;
  };
  componentDidMount() {
    TimePlay.addFunc(this.updateState);
  }
  render() {
    if (this.state.isShowChildren) {
      return this.props.children;
    } else {
      return null;
    }
  }
}

通过此方法,如果在实际业务当中两分钟内撤回了很多条消息,依旧只需要静态方法中的一个定时器去轮询,而无需同时开启多个定时器。

当然,如果对render中的return结果进行改造,还可以应用于抢购或其他场景下同个页面有多个倒计时的场景。

如果你有更好的解决方案,欢迎讨论~

欢迎关注


LHH大翰仔仔
2k 声望2.6k 粉丝