用 React 封装一个可暂停并根据剩余时间继续的计时器集合钩子

我正在参加「掘金·启航计划」

useKeepIntervalMap

这个钩子用于需要统一管理多个可中途暂停并继续的计时器的场景;比如在游戏中多个函数需要统一暂停,并根据剩余时间继续开始。

我的这个塔防小游戏中的塔防射击,敌人减速和技能cd等的暂停与继续,就是使用了该钩子。

该钩子跟我的上一篇文章是类似的。不过上一篇主要用于一个计时器,而这里是多个计时器的统一管理。

源码

使用文档

demo预览

实现过程

数据定义

定义一个 map 集合,需要执行的函数都将有对应的一个唯一的 key 值。

const timerMap = useRef<Map<string, UseKeepIntervalItem>>(new Map())

map 集合存放的数据。

export type UseKeepIntervalItem = {
  /** 第一层的setTimeout */
  timeout: NodeJS.Timeout | null
  /** 第二层的setInterval */
  interval: NodeJS.Timeout | null
  /** 当前时间 */
  cur: number
  /** 暂停时间 */
  end: number
  /** 传入的执行函数 */
  fn: () => void
  /** 固定的时间间隔 */
  intervalTime: number
  /** 用于setTimeout的剩余时间间隔 */
  remainTime: number
  /** 是否只是倒计时 */
  isTimeOut: boolean
}

开始计时器

将传入的 keyfn ,传入到 map 集合中保存下来,紧接着通过 setTimeout 包裹 setInterval 的方式来实现倒计时的功能。其中需要处理好对应的开始和暂停等的时间,通过这些时间判断倒计时剩余时间即可。由于篇幅问题,我这里精简了一下,建议直接看源码。

const set = useCallback((
  key: string,
  fn?: () => void, 
  intervalTime = 0, 
  {isTimeOut = false, isCover, isInit}: KeepIntervalSetOthParams = {}
) => {
  stopTime(key)
  if((!timerMap.current.has(key) || isCover) && fn) {
    timerMap.current.set(key, {
      timeout: null,
      interval: null,
      cur: 0,
      end: 0,
      fn,
      intervalTime,
      remainTime: intervalTime,
      isTimeOut,
    })
  }
  // ...
  timeItem.timeout = setTimeout(() => { 
    timeItem.interval = setInterval(() => { 
      // ...
      timeItem.fn() 
    }, timeItem.intervalTime)
    timeItem.fn()
  }, timeItem.remainTime)
}, []) 

暂停计时器

根据传入的 key 值,暂停 map 集合中的某个函数的执行。

/** 暂停计时器 */
const pause = useCallback((key: string) => {
  const timeItem = timerMap.current.get(key)
  if(timeItem) {
    timeItem.end = Date.now()
    stopTime(key)
    return timeItem.remainTime - (timeItem.end - timeItem.cur)
  }
}, [])
/** 停止定时器 */
const stopTime = useCallback((key: string) => {
  const timeItem = timerMap.current.get(key)
  if(timeItem?.timeout) {
    clearTimeout(timeItem.timeout)
    timeItem.timeout = null
  }
  if(timeItem?.interval) {
    clearInterval(timeItem.interval)
    timeItem.interval = null
  }
}, [])

map 集合中的函数全部暂停或开始。

/** 全部暂停或开始 */
const allPause = useCallback((isPause = true) => {
  timerMap.current.forEach((_, key) => {
    isPause ? pause(key) : set(key)
  })
}, [])

销毁计时器

通过传入的 key 清除 map 集合中对应的计时器和数据。

/** 删除其中一个 */
const deleteFn = useCallback((key: string) => {
  stopTime(key)
  if(timerMap.current.has(key)) {
    timerMap.current.delete(key)
  }
}, [])

组件销毁的时候清空 map 集合中的所有计时器和数据,防止内存泄漏。

useEffect(() => {
  return () => clear()
},[])
/** 清空数据 */
const clear = useCallback(() => {
  timerMap.current?.forEach((_, key) => {
    stopTime(key)
  })
  timerMap.current.clear()
}, [])

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYM7l8Sj' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片