Skip to content

useRafInterval

作用

一个可以处理 setInterval 的 Hook, 用于代替 setInterval。

原理

requestAnimationFrame 模拟实现 setIntervalAPIuseInterval 保持一致,好处是可以在页面不渲染的时候停止执行定时器,比如页面隐藏或最小化等.

请注意,如下两种情况下很可能是不适用的,优先考虑 useInterval

  • 时间间隔小于 16ms
  • 希望页面不渲染的情况下依然执行定时器

源码

ts
import { useCallback, useEffect, useRef } from 'react'
import useLatest from '../useLatest'
import { isNumber } from '../utils'

interface Handle {
  id: number | NodeJS.Timer
}

// 用 requestAnimationFrame 模拟实现 setInterval
const setRafInterval = function (callback: () => void, delay: number = 0): Handle {
  // 如果不支持 requestAnimationFrame,则使用 setInterval
  if (typeof requestAnimationFrame === typeof undefined) {
    return {
      id: setInterval(callback, delay)
    }
  }
  let start = new Date().getTime()
  const handle: Handle = {
    id: 0
  }
  // 调用 requestAnimationFrame生成动画
  const loop = () => {
    const current = new Date().getTime()
    if (current - start >= delay) {
      callback()
      start = new Date().getTime()
    }
    handle.id = requestAnimationFrame(loop)
  }
  handle.id = requestAnimationFrame(loop)
  return handle
}

// 判断是否支持cancelAnimationFrame
function cancelAnimationFrameIsNotDefined(t: any): t is NodeJS.Timer {
  return typeof cancelAnimationFrame === typeof undefined
}

const clearRafInterval = function (handle: Handle) {
  // 不支持cancelAnimationFrame就用clearInterval
  if (cancelAnimationFrameIsNotDefined(handle.id)) {
    return clearInterval(handle.id)
  }
  // 支持就用cancelAnimationFrame清除动画
  cancelAnimationFrame(handle.id)
}

function useRafInterval(
  fn: () => void,
  delay: number | undefined,
  options?: {
    immediate?: boolean
  }
) {
  const immediate = options?.immediate

  const fnRef = useLatest(fn)
  const timerRef = useRef<Handle>()

  useEffect(() => {
    if (!isNumber(delay) || delay < 0) return
    if (immediate) {
      fnRef.current()
    }
    // 使用setRafInterval来执行回调
    timerRef.current = setRafInterval(() => {
      fnRef.current()
    }, delay)
    return () => {
      if (timerRef.current) {
        // 清除定时器
        clearRafInterval(timerRef.current)
      }
    }
  }, [delay])

  // 返回清除定时器的函数
  const clear = useCallback(() => {
    if (timerRef.current) {
      clearRafInterval(timerRef.current)
    }
  }, [])

  return clear
}

export default useRafInterval

如有转载或 CV 的请标注本站原文地址