[英]Why is passing function argument to React.useState and the return function will return stale value
Give the codes below给出下面的代码
import { useState } from "react";
import "./styles.css";
export default function App() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const inc1 = () => {
console.log("debug inc:", count1);
setCount1((prev) => prev + 1);
};
const inc2 = () => {
console.log("debug inc2:", count2);
setCount2(count2 + 1);
};
const [processInc1] = useState(() => {
console.log("debug longProcessBeforeInc:", count1);
// Run some long process here
return inc1;
});
const [processInc2] = useState(() => {
console.log("debug longProcessBeforeInc:", count2);
// Run some long process here
return inc2;
});
console.log("debug render:", count1, count2);
return (
<div className="App">
<h3>
{count1} - {count2}
</h3>
<button onClick={inc1}>Inc 1</button>
<br />
<br />
<button onClick={inc2}>Inc 2</button>
<br />
<br />
<button onClick={processInc1}>Long Inc 1</button>
<br />
<br />
<button onClick={processInc2}>Long Inc 2</button>
</div>
);
}
inc1
, inc2
, processInc1
all works as expected where you increase value by 1 and it renders correctly. inc1
, inc2
, processInc1
都按预期工作,您将值增加 1 并正确呈现。
So with inc1
, inc2
the main difference is setCount1((prev) => prev + 1);
所以
inc1
, inc2
的主要区别是setCount1((prev) => prev + 1);
and setCount2(count2 + 1);
和
setCount2(count2 + 1);
, and processInc1
, processInc2
basically return inc1
and inc2
respectively via useState first time the component renders. , 和
processInc1
, processInc2
基本上在组件第一次渲染时分别通过 useState 返回inc1
和inc2
。
I understand from here https://reactjs.org/docs/hooks-faq.html#why-am-i-seeing-stale-props-or-state-inside-my-function that it has something to do with closure but given the example above I fail to wrap my head around why inc2
, and processInc1
works but not processInc2
?我从这里了解到https://reactjs.org/docs/hooks-faq.html#why-am-i-seeing-stale-props-or-state-inside-my-function它与关闭有关,但是鉴于上面的示例,我无法理解为什么
inc2
和processInc1
有效但processInc2
无效?
Here is the link to the codesandbox
for the abovehttps://codesandbox.io/s/eloquent-morse-37xyoy?file=/src/App.js这是上述https://codesandbox.io/s/eloquent-morse-37xyoy?file=/src/App.js的
codesandbox
链接
You are on the right path.你走在正确的道路上。 As you were saying, this problem is actually caused by the use of the closure.
正如您所说,这个问题实际上是由使用闭包引起的。 So let's start by showing you a nice definition of closure:
因此,让我们首先向您展示一个很好的闭包定义:
A closure is the combination of a function and the lexical environment within which that function was declared.
闭包是函数和声明该函数的词法环境的组合。 This environment consists of any local variables that were in-scope at the time the closure was created.
该环境由创建闭包时在范围内的任何局部变量组成。
Docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures文档: https ://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
This means that when you return the closure inc2 in the useState, you are also creating a copy of the variables that are used at that particular time (ie initial rendering) by that lexical environment (this includes the count2 value).这意味着当您在 useState 中返回闭包inc2时,您还创建了该词法环境在特定时间(即初始呈现)使用的变量的副本(这包括 count2 值)。 This is why processInc2 will keep having the same old count2 value.
这就是为什么processInc2将保持相同的旧 count2 值。
At the same time processInc1 function will work correctly because, by using a callback in the useState, you are always getting the current value of the count1 state.同时processInc1函数将正常工作,因为通过在 useState 中使用回调,您始终可以获取 count1 状态的当前值。
Lastly, inc2 works because you are directly calling it when you click the button, so the count2 value gets evaluated at that moment that you call it (therefore it will most probably have the current value).最后, inc2有效,因为您在单击按钮时直接调用它,因此在您调用它的那一刻计算 count2 值(因此它很可能具有当前值)。
A very important detail here is that inc1
and inc2
are re-defined each time the App
component renders.这里一个非常重要的细节是,每次
App
组件渲染时都会重新定义inc1
和inc2
。
function App() {
// ...
const inc1 = () => {
console.log("debug inc:", count1);
setCount1((prev) => prev + 1);
};
// ...
}
You then store those 2 functions inside a state, that does never change.然后,您将这两个函数存储在一个永远不会改变的状态中。
const [processInc1] = useState(() => {
console.log("debug longProcessBeforeInc:", count1);
// Run some long process here
return inc1;
});
This will result in processInc1
and processInc2
that point to the very first definition of inc1
and inc2
(created on the first render).这将导致
processInc1
和processInc2
指向inc1
和inc2
的第一个定义(在第一次渲染时创建)。
The reason count1
and count2
never update in this first version of the function is because the variable(s) are never re-assigned.在函数的第一个版本中,
count1
和count2
永远不会更新的原因是因为永远不会重新分配变量。 This is by design.这是设计使然。
The only reason count1
and count2
change in a future render is because useState()
will return the new value. count1
和count2
在未来渲染中发生变化的唯一原因是useState()
将返回新值。 After you receive this new value inc1
and inc2
are re-defined.收到这个新值后,
inc1
和inc2
被重新定义。
processInc1
and processInc2
are then pulled out of a React state that holds the first definition of inc1
and inc2
, so usage of count1
and count2
inside those functions will refer to the first value of count1
and count2
.然后
processInc1
和processInc2
被拉出包含inc1
和inc2
的第一个定义的 React 状态,因此在这些函数中使用count1
和count2
将引用count1
和count2
的第一个值。
When you do setCount2(count2 + 1)
inside inc2
and call it via processInc2
the value of count2
is still 0
and will never change.当您在 inc2 中执行
inc2
setCount2(count2 + 1)
并通过processInc2
调用它时, count2
的值仍然是0
并且永远不会改变。 This is because processInc2
refers to the very first definition of inc2
, and not the current definition.这是因为
processInc2
指的是inc2
的第一个定义,而不是当前定义。
setCount1((prev) => prev + 1)
works due to the different function signature. setCount1((prev) => prev + 1)
由于不同的函数签名而起作用。 Where inc2
passes a static value ( 0 + 1
) to the setter, inc1
passes a transformation (as a callback).其中
inc2
将静态值 ( 0 + 1
) 传递给 setter,而inc1
则传递转换(作为回调)。 When you pass a function to setCount1
React will call that function with the current state as the sole argument.当你将一个函数传递给
setCount1
时,React 会以当前状态作为唯一参数调用该函数。 The return value is then used as the new state.然后将返回值用作新状态。 So although
processInc1
still uses the very first definition of inc1
.因此,尽管
processInc1
仍然使用inc1
的第一个定义。 It will always be relevant, since it describes the transformation that must be made rather than the value that must be set.它总是相关的,因为它描述了必须进行的转换,而不是必须设置的值。
Do note that console.log("debug inc:", count1);
请注意
console.log("debug inc:", count1);
will keep logging 0
when called via processInc1
for the above reasons.由于上述原因,通过
processInc1
调用时将保持记录0
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.