为什么useRef解决了过时状态的问题



我知道React组件内嵌套函数或回调(例如setTimeout(中存在过时状态的问题。例如,在下面的代码(取自React文档(中,我们看到了过时状态的问题:

"如果您首先单击"显示警报",然后递增计数器警报将显示您单击"显示"时的计数变量警报"按钮">

function Example() {
const [count, setCount] = useState(0);
function handleAlertClick() {
setTimeout(() => {
alert('You clicked on: ' + count);
}, 3000);
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
<button onClick={handleAlertClick}>
Show alert
</button>
</div>
);
}

我知道我们可以使用useRef:创建一个对状态的最新/当前值的可变引用

function Example() {
const [count, setCount] = useState(0);
const stateRef = useRef()
stateRef.current = count
function handleAlertClick() {
setTimeout(() => {
alert('You clicked on: ' + stateRef.current);
}, 3000);
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
<button onClick={handleAlertClick}>
Show alert
</button>
</div>
);
}

我的问题是,为什么创建对useRef变量(stateRef(的引用可以解决过时状态的问题,为什么可以绕过闭包的问题?当然,通过直接在setTimeout中引用count,一旦组件重新呈现count值,就会更新,因此我们对它的引用将返回最新值。

我很难理解为什么引用stateRef而不是count会绕过过时状态的问题,因为从setTimeout的角度来看,它们都是在同一词汇范围内声明的。

试着想象一个时间线,每次重新渲染组件都会创建一个"快照";并在这个时间线上留下标记。

所谓的";陈旧状态";更经常被称为";"陈旧的闭合";,这更准确地描述了真实的问题。

关闭究竟是如何造成麻烦的?

每次组件重新渲染时,组件功能本身都会从头到尾重新执行,在执行过程中:

  1. 一路上所有的钩子调用都被调用
  2. 所有局部变量都被重新初始化
  3. 并且重新声明组件函数内的所有嵌套函数
  4. 而且最重要的是,如果这些嵌套函数中的任何一个引用了组成函数中的局部变量,那么这种引用实际上指向"局部变量";"闭合";,或一个";快照";它捕获那些动态创建并存储在内存中的局部变量的当前状态

如果您调用setTimeout并向其发送回调函数,并且该函数引用了一个局部变量,那么它实际上存储在渲染时间线上的其中一个快照中。

当组件重新渲染时,新的快照会添加到时间线中,并且只有最新的快照是最新的,而回调函数仍在引用过时的快照/闭包。

setTimeout决定执行回调时,回调查看它自己的快照/闭包版本,并说";好的,"count"变量是0;没有意识到它已经过时了。

useRef如何解决问题?

Cusconst ref = useRef()在重新渲染过程中始终返回相同的对象引用。因此,即使回调查看过时的快照/闭包,它仍然会看到与最新快照中相同的对象ref。由于每次重新执行组件函数都会将ref.value = someValue属性设置为最新值,因此回调获得了访问最新值的方法。

当然,只要直接在setTimeout内引用count组件重新渲染计数值将更新,因此我们引用它将返回最新的值。

不,当您单击按钮时,函数useSetTimeout使用了当时手头的回调。并且count不是对您的变量的引用。当组件重新调用时,回调根本不知道您更改了值。

useRef的不同之处在于它返回一个对象,对象的工作方式与其他类型的变量有点不同。因为它总是同一个对象,所以你总是要处理同一个变量。

顺便说一句,当你打setCount时,一定要一直打setCount(prevState => ...)。否则,不能保证您拥有最新的价值。

最新更新