在 React 中使用请求动画帧

问题描述

我正在阅读 this article,但我不确定我是否理解最终钩子的工作原理。

代码如下:

const useAnimationFrame = (callback) => {
  const requestRef = useRef();
  const prevIoUsTimeRef = useRef();

  const animate = (time) => {
    if (prevIoUsTimeRef.current !== undefined) {
      const deltaTime = time - prevIoUsTimeRef.current;
      callback(deltaTime);
    }
    prevIoUsTimeRef.current = time;
    requestRef.current = requestAnimationFrame(animate);
  };

  useEffect(() => {
    requestRef.current = requestAnimationFrame(animate);
    return () => cancelAnimationFrame(requestRef.current);
  },[]);
}

例如以这种方式使用:

const [count,setCount] = useState(0);

useAnimationFrame((deltaTime) => {
  setCount((prevCount) => {
    return prevCount + 1;
  });
});

好的,目标是有一个每帧递增的数值。

我可以解释运行这段代码会发生什么:

  1. 组件使用 useState(0)

    创建本地状态
  2. 然后使用此回调作为参数调用 useAnimationFrame 钩子:

    (deltaTime) => {
      setCount((prevCount) => {
        return prevCount + 1;
      });
    }
    

函数一个数字作为输入,每次调用时都会将 ste 状态值增加 1。

  1. useAnimationFrame一个将另一个函数作为参数(回调)的函数。它创建了两个参考。在第一次执行时(因为 []),它调用 useEffect。它将 requestRef.current 返回的时间戳保存在 requestAnimationFrame 中。 requestRef.current 调用 animate 函数,该函数计算请求动画帧(前一帧和当前帧)之间的时间差,然后使用此值调用回调,因此它调用 setCount。然后它更新当前的 refs 值并调用 requestAnimationFrame

所以循环应该是:

component 
  > count = 0
useAnimationFrame             <--------------+
  > requestRef = ?                           |
  > prevIoUsTimeRef = ?                      |
    useEffect                                |
      animate                                |
        > deltaTime = delta#1                |
        > count = 1                          |
        > prevIoUsTimeRef.current = time#1   |
        > requestRef.current = time#2 -------+
      > requestRef.current = timestamp#1

我错了吗?

解决方法

跟踪 requestAnimationFramecancelAnimationFrame 的函数签名可能会有所帮助。

requestAnimationFrame 接受一个参数,一个回调函数。回调函数本身接收单个时间戳参数 (DOMHighResTimeStamp)

cancelAnimationFrame 接受一个参数,即您要取消的 idrequestAnimationFrame

所以 time 回调函数中的 animate 是通过 api 接收的单个参数,a DOMHighResTimeStamp similar to the one returned by performance.now(),indicating the point in time when requestAnimationFrame() starts to execute callback functions.

 const animate = (time) => {

这是检查钩子是否已经运行了 1 次。如果有,则用新时间减去前一时间更新父 React 作用域

    if (previousTimeRef.current !== undefined) {
      const deltaTime = time - previousTimeRef.current;
      callback(deltaTime);
    }

一旦钩子被确认运行,保存DOMHighResTimeStamp以备将来计算

    previousTimeRef.current = time;

此后,它变得有点有趣,我不确定这是最好的方法。它甚至可能是一个错误。该代码设置了一个新的侦听器,并根据新调用的结果使用最新的 ID 更新 ref

仅通过阅读代码,我不确定原始侦听器是否会得到 cancelled。我怀疑不是。

    /// this is an id
    requestRef.current = requestAnimationFrame(animate);

我无权访问正在运行的版本,但我建议完全删除 requestRef.current 并查看在 useEffect 清理发生时清理是否按预期进行,例如>

  useEffect(() => {
    const id = requestAnimationFrame(animate);
    return () => cancelAnimationFrame(id);
  },[]);

这也将简化嵌入的 refs 以及使阅读更清晰。