如何用一個定時器維護多個須要實時更新的效果

場景重現react

以前在作一個IM的模塊,有一個撤回消息後兩分鐘以內能夠從新編輯的功能,項目是用react作的,按照正常的思路,只需傳入一個消息撤回的時間markTime,而後用如今時間Date.now()去判斷是否已經超時兩分鐘。然而理想老是美好的,因爲沒有去觸發react的從新渲染機制,即便超時了從新編輯的按鈕也不會消失,等到去點擊從新編輯按鈕再提示已超時的話這個極大的影響用戶體驗。性能優化

解決方案性能

  • 思路I

能夠給每條撤回的系統消息加上一個setInterval定時器輪詢,只要未超時就實時更新當前時間,直至超時再把這個定時器清除掉;
可是這個方法有個弊端,就是假設我在有限時間內撤回了很是多條消息,那麼這很是多條的消息就會有着對應的多個定時器在工做着,這對於性能的損耗特別的大,對於本公司的項目,團隊在性能優化上仍是有追求的,因此思路I被否決掉了;優化

  • 思路II

經過靜態方法維護同一個定時器訂閱器,若是該訂閱器中還有存在的未超過有限時間的事件,則展現其中未超過有限時間的子組件,若是已超時,則不展現其對應的自組件,返回一個null
上代碼ui

/** * 經過靜態方法維護同一個定時器,傳入標記時間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;
    }
  }
}
複製代碼

經過此方法,若是在實際業務當中兩分鐘內撤回了不少條消息,依舊只須要靜態方法中的一個定時器去輪詢,而無需同時開啓多個定時器。this

固然,若是對render中的return結果進行改造,還能夠應用於搶購或其餘場景下同個頁面有多個倒計時的場景。spa

若是你有更好的解決方案,歡迎討論~code

相關文章
相關標籤/搜索