将requestAnimationFrame与React Hooks一起使用

目录
文章目录隐藏
  1. useRef 不仅用于 DOM 引用
  2. useEffect 的副作用
  3. 状态的 setter 函数也接受一个函数
  4. 把它们放在一起
  5. 更新:使用自定义钩子

将 requestAnimationFrame 与 React Hooks 一起使用
使用 requestAnimationFrame 进行动画应该很容易,但是如果你还没有深刻完整的阅读 React 的文档,那么你可能会遇到一些让你头疼的事情,以下是我在学习中采坑记录。

传递一个空数组作为 useEffect 的第二个参数,以避免它多次运行,并将一个函数传递给状态的 setter 函数,以确保始终具有正确的状态。另外,使用 useRef 存储时间戳和请求的 ID 等内容。

useRef 不仅用于 DOM 引用

在功能组件中存储变量有三种方法:

  • 我们可以定义一个简单的 const 或 let,它的值总是随着每个组件的重新呈现而重新初始化。
  • 我们可以使用 useState,它的值在重新呈现期间保持不变,如果你更改它,它还将触发重新呈现。
  • 我们可以使用 useRef。

useRef hook 主要用于访问 DOM,但还不止这些。它是一个可变的对象,在多个重新呈现中持久化一个值。它与 useState hook 非常相似,只是您可以通过它的.current 属性读写它的值,并且更改它的值不会重新呈现组件。

例如,下面的示例将始终显示 5,即使组件由其父组件重新呈现。

function Component() {
  let variable = 5;

  setTimeout(() => {
    variable = variable + 3;
  }, 100)

  return <div>{variable}</div>
}

而这一个将继续增加 3 个数字,并继续重新呈现,即使父元素不变。

function Component() {
  const [variable, setVariable] = React.useState(5);

  setTimeout(() => {
    setVariable(variable + 3);
  }, 100)

  return <div>{variable}</div>
}

最后,这个返回 5,不会重新渲染。但是,如果父类触发了重新呈现,那么每次都会增加一个值(假设重新呈现发生在 100 毫秒之后)。

function Component() {
  const variable = React.useRef(5);

  setTimeout(() => {
    variable.current = variable.current + 3;
  }, 100)

  return <div>{variable.current}</div>
}

如果我们有可变的值,我们想要记住在下一次或以后的渲染,我们不希望他们触发重新渲染当他们改变时,那么我们应该使用 useRef。在我们的例子中,我们将需要不断变化的 request animation frame ID 用于清理,如果我们基于循环之间传递的时间进行动画,那么我们需要记住之前动画的时间戳。这两个变量应该存储为 refs。

useEffect 的副作用

我们可以使用 useEffect 钩子来初始化和处理我们的请求,尽管我们想确保它只运行一次; 否则它将最终在每次渲染时创建,取消和重新创建动画帧请求。这是一个有效但不好的例子:

function App() {
  const [state, setState] = React.useState(0)

  const requestRef = React.useRef()
  
  const animate = time => {
    // Change the state according to the animation
    requestRef.current = requestAnimationFrame(animate);
  }
    
  // DON’T DO THIS
  React.useEffect(() => {
    requestRef.current = requestAnimationFrame(animate);
    return () => cancelAnimationFrame(requestRef.current);
  });
  
  return <div>{state}</div>;
}

为什么不好呢?如果你运行这个,useEffect 将触发 animate 函数,该函数将更改状态并请求一个新的动画帧。这听起来不错,但是状态更改将通过再次运行整个函数重新呈现组件,其中包括 useEffect 钩子,该钩子将首先作为清理取消 animate 函数在前一个循环中发出的请求,然后启动一个新请求。这最终取代了 animate 函数的请求,这是完全不必要的。我们可以通过不在 animate 函数中旋转一个新请求来避免这种情况,但是那样仍然不是很好。它仍然会在每一轮中给我们留下不必要的清理,如果组件由于其他原因重新呈现—比如父组件重新呈现它或其他状态发生了更改—那么不必要的取消和请求重新创建仍然会发生。更好的模式是只初始化请求一次,让它们通过 animate 函数旋转,然后在组件卸载时清除一次。

为了确保 useEffect 钩子只运行一次,我们可以将一个空数组作为第二个参数传递给它。不过,传递一个空数组有一个副作用,它避免了我们在动画期间拥有正确的状态。第二个参数是一个更改值列表,该效果需要对这些值作出反应。我们不想对任何东西做出反应——我们只想初始化动画——因此我们有了空数组。但是 React 会以一种方式来解释这一点,这意味着这种效果并不一定要与国家同步。这包括 animate 函数,因为它最初是从 effect 中调用的。因此,如果我们试图在 animate 函数中获取状态值,它总是初始值。如果我们想根据它之前的值和时间来改变状态,那么它可能不会工作。

function App() {
  const [state, setState] = React.useState(0)

  const requestRef = React.useRef()
  
  const animate = time => {
    // The 'state' will always be the initial value here
    requestRef.current = requestAnimationFrame(animate);
  }
    
  React.useEffect(() => {
    requestRef.current = requestAnimationFrame(animate);
    return () => cancelAnimationFrame(requestRef.current);
  }, []); // Make sure the effect runs only once
  
  return <div>{state}</div>;
}

状态的 setter 函数也接受一个函数

即使 useEffect 钩子将状态锁定为初始值,也可以使用最新状态。useState 钩子的 setter 函数也可以接受函数。所以,与其像大多数情况下那样,根据当前状态传递值,不如:

setState(state + delta)

你还可以传递一个函数,该函数接收前一个值作为参数。即使在我们的情况下,它也会返回正确的值:

setState(prevState => prevState + delta)

把它们放在一起

下面是一个简单的例子。我们将把上面所有的放在一起,创建一个计数器,计数到 100,然后从头开始。我们希望在不重新呈现整个组件的情况下持久化和修改的技术变量存储在 useRef 中。我们通过传递一个空数组作为它的第二个参数来确保 useEffect 只运行一次。我们通过传递一个函数给 useState 的 setter 来改变状态以确保我们始终拥有正确的状态。

点击运行

更新:使用自定义钩子

一旦基本的东西都弄清楚了,我们也可以通过将大部分逻辑提取到一个定制的钩子中来使用钩子元数据。这样做有两个好处:

  • 它极大地简化了我们的组件,隐藏了与动画相关的技术变量,但与我们的主逻辑无关
  • 自定义钩子是可重用的。如果需要在另一个组件中使用动画,也可以在那里使用

自定义钩子一开始听起来像是一个高级主题,但最终我们只是将一部分代码从组件移动到一个函数,然后像调用其他函数一样在组件中调用该函数。作为一种约定,自定义钩子的名称应该以 use 关键字开头,并应用钩子的规则,否则,它们只是一些简单的函数,我们可以使用输入自定义它们,并可能返回一些内容。

在我们的例子中,要为 requestAnimationFrame 创建一个通用的钩子,我们可以传递一个回调,这个回调是自定义钩子在每个动画周期中调用的。这样,我们的主要动画逻辑将留在组件中,但组件本身将更加集中。

点击运行

作者:Hunor Márton Borbély

翻译:码云

原文:点击链接

「点点赞赏,手留余香」

8

给作者打赏,鼓励TA抓紧创作!

微信微信 支付宝支付宝

还没有人赞赏,快来当第一个赞赏的人吧!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。
码云笔记 » 将requestAnimationFrame与React Hooks一起使用

发表回复