由于刚使用 React hooks 不久,对它的脾气还拿捏不准,掉了很多次“坑”;这里的 “坑” 的意思并不是说 React hooks 的设计有问题,而是我在使用的时候,因为还没有跟上它的理念导致的一些问题。
在读了一些文章后,大致是找到自己总是掉坑的原因了 —— 没理解 React Hooks 中的 Capture Value 特性。
本文就以简单的示例来解释这个特性所产生的现象,对理解 Capture Value 特性做一个补充。
“这个 effects 取的值怎么不是最新的?!”这个疑惑可以说是在使用 React Hooks 时经常遇到的疑问。
在下列代码中,当你点击按钮 3s 后,alert 显示的数值却是 3s 前的 count 变量 —— 即无法获取最新的值,获取的值是过去某个时刻的:
import React, { useState, useCallback } from "react";
import ReactDOM from "react-dom";
function Example() {
const [count, setCount] = useState(0);
const handleAlertClick = useCallback(()=>{
setTimeout(() => {
alert('You clicked on: ' + count);
}, 3000)
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
增加 count
</button>
<button onClick={handleAlertClick}>
显示 count
</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Example />, rootElement);
示例代码:https://codesandbox.io/s/k5pmk0omx7
具体操作步骤:
3s 后看到的弹层计数仍旧为 0
这是官方特意设置的机制,官方原文是:This prevents bugs caused by the code assuming props and state don’t change;(强行翻译一下,大概意思是:防止因 React 认为 props
或者 state
没有变更而引起的 bug)
为了理解官方这么设定的意图,将上面代码稍微修改一下:
显示 count
按钮减少 count
的按钮useEffect
代替 useCallback
,让每次更改 count 都会弹窗...
useEffect(()=>{
setTimeout(() => {
alert('count: ' + count);
}, 3000)
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
增加 count
</button>
<button onClick={() => setCount(count - 1)}>
减少 count
</button>
</div>
);
}
...
我们先点击一次 增加 count
,然后再紧接着点击一次 减少 count
:
count
数值都是 0 —— 很明显这不是我们想要的会先显示 1,然后显示 0
总结起来,这个现象其实就是文章 精读《useEffect 完全指南》 所提及的 Capture Value 特性(可以自行前往原文了解更多细节)
回到原来的问题,倔强如我,我就是想要在 3s 后获取的是此时此刻的 count
变量,而不是我 3s 前点击时的 count
值,该怎么操作?
官方给出的解决方案是,每次改变 count
的时候,将其放在 ref 类型的变量里即可。
修改一下原来的代码:
const countRef = useRef(null);
const handleAlertClick = useCallback(
() => {
setTimeout(() => {
alert("You clicked on: " + countRef.current);
}, 3000);
},
[count]
);
return (
<div>
<p>You clicked {count} times</p>
<button
onClick={() => {
countRef.current = count + 1;
setCount(count + 1);
}}
>
增加 count
</button>
<button onClick={handleAlertClick}>显示 count</button>
</div>
);
更改过后的代码运行后,3s
后 alert 显示的 count
变量就是你页面上所见到的样子了:
获取即刻的 count 变量
ref 类型的变量通常是用来存储 DOM 元素引用,但在 react hooks 中,它可以存放任何可变数据,就好比类实例属性一样,具体参考 Is there something like instance variables?
这等操作,其实就是借助 ref
类型变量绕过 Capture Value 特性来达到目的。
援引文章 精读《useEffect 完全指南》 中对 Capture Value 概念的解释:每次 Render 的内容都会形成一个快照并保留下来,因此当状态变更而 Rerender 时,就形成了 N 个 Render 状态,而每个 Render 状态都拥有自己固定不变的 Props 与 State。
通过这个示例,相信会比较容易地理解 Capture Value 特性,并如何使用 ref
来暂时绕过它。在知道并理解这个特性后,有助于进一步熟悉了 React Hooks 的运行机制,减少掉坑的次数。
REFERENCE
参考文档
setInterval
和 Hooks
和谐地玩耍,为什么是这种方式,以及这种方式给你带来了什么新能力。useRef
来解决,也有人针对它进行了封装(How to compare oldValues and newValues on React Hooks useEffect?)—END—