码云笔记前端博客
Home > JavaScript > 将requestAnimationFrame与React Hooks一起使用

将requestAnimationFrame与React Hooks一起使用

2019-09-02 分类:JavaScript 作者:码云 阅读(62)

本文共计3406个字,预计阅读时长需要9分钟。

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

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

useRef不仅用于DOM引用

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

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

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

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

1
2
3
4
5
6
7
8
9
function Component() {
  let variable = 5;

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

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

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

1
2
3
4
5
6
7
8
9
function Component() {
  const [variable, setVariable] = React.useState(5);

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

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

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

1
2
3
4
5
6
7
8
9
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钩子来初始化和处理我们的请求,尽管我们想确保它只运行一次; 否则它将最终在每次渲染时创建,取消和重新创建动画帧请求。这是一个有效但不好的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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函数中获取状态值,它总是初始值。如果我们想根据它之前的值和时间来改变状态,那么它可能不会工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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函数也可以接受函数。所以,与其像大多数情况下那样,根据当前状态传递值,不如:

1
setState(state + delta)

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

1
setState(prevState => prevState + delta)

把它们放在一起

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

点击运行

更新:使用自定义钩子

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

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

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

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

点击运行

「除特别注明外,本站所有文章均为码云笔记原创,转载请保留出处!」

赞(7) 打赏

觉得文章有用就打赏一下文章作者

支付宝
微信
7

觉得文章有用就打赏一下文章作者

支付宝
微信

上一篇:

下一篇:

你可能感兴趣

共有 0 条评论 - 将requestAnimationFrame与React Hooks一起使用

博客简介

码云笔记网 mybj123.com,一个专注Web前端开发技术的博客,主要记录和总结博主在前端开发工作中常用的实战技能及前端资源分享,分享各种科普知识和实用优秀的代码,以及分享些热门的互联网资讯和福利!码云笔记网有你更精彩!
更多博客详情请看关于博客

精彩评论