为什么将函数参数传递给 React.useState 而 return 函数将 return 过时值
Why is passing function argument to React.useState and the return function will return stale value
给出下面的代码
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
都按预期工作,您将值增加 1 并正确呈现。
所以与inc1
、inc2
的主要区别是setCount1((prev) => prev + 1);
和setCount2(count2 + 1);
,而processInc1
、processInc2
基本上是return inc1
和 inc2
分别在组件第一次渲染时通过 useState。
我从这里 https://reactjs.org/docs/hooks-faq.html#why-am-i-seeing-stale-props-or-state-inside-my-function 了解到它与闭包有关,但鉴于上面的示例,我无法理解为什么 inc2
和 processInc1
有效但无效processInc2
?
这里是上面的link到codesandbox
https://codesandbox.io/s/eloquent-morse-37xyoy?file=/src/App.js
你走在正确的道路上。正如你所说,这个问题实际上是由闭包的使用引起的。那么让我们首先向您展示闭包的一个很好的定义:
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.
文档:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
这意味着当您 return 在 useState 中关闭 inc2 时,您还创建了在该特定时间使用的变量的副本(即初始渲染)由那个词法环境(这包括 count2 值)。这就是为什么 processInc2 将保持相同的旧 count2 值。
同时 processInc1 函数将正常工作,因为通过在 useState 中使用回调,您始终可以获得当前count1 状态的值。
最后,inc2 起作用是因为您在单击按钮时直接调用它,因此 count2 值会在那一刻得到评估您调用它(因此它很可能具有当前值)。
这里一个非常重要的细节是 inc1
和 inc2
每次 App
组件渲染时都是 re-defined。
function App() {
// ...
const inc1 = () => {
console.log("debug inc:", count1);
setCount1((prev) => prev + 1);
};
// ...
}
然后将这 2 个函数存储在一个状态中,该状态永远不会改变。
const [processInc1] = useState(() => {
console.log("debug longProcessBeforeInc:", count1);
// Run some long process here
return inc1;
});
这将导致 processInc1
和 processInc2
指向 inc1
和 inc2
的第一个定义(在第一次渲染时创建)。
count1
和 count2
在函数的第一个版本中从不更新的原因是变量从不 re-assigned。这是设计使然。
count1
和 count2
在未来的渲染中改变的唯一原因是因为 useState()
将 return 新值。收到此新值后 inc1
和 inc2
为 re-defined.
然后 processInc1
和 processInc2
被拉出包含 inc1
和 inc2
的第一个定义的 React 状态,因此 count1
的用法这些函数中的 count2
将引用 count1
和 count2
.
的第一个值
当你在 inc2
中执行 setCount2(count2 + 1)
并通过 processInc2
调用它时, count2
的值仍然是 0
并且永远不会改变。这是因为 processInc2
指的是 inc2
的第一个定义,而不是当前定义。
setCount1((prev) => prev + 1)
由于不同的函数签名而起作用。其中 inc2
将静态值 (0 + 1
) 传递给 setter,inc1
传递一个转换(作为回调)。当您将函数传递给 setCount1
时,React 将使用当前状态作为唯一参数调用该函数。然后 return 值用作新状态。所以尽管 processInc1
仍然使用 inc1
的第一个定义。它总是相关的,因为它描述了必须进行的转换而不是必须设置的值。
请注意,出于上述原因,当通过 processInc1
调用时,console.log("debug inc:", count1);
将继续记录 0
。
给出下面的代码
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
都按预期工作,您将值增加 1 并正确呈现。
所以与inc1
、inc2
的主要区别是setCount1((prev) => prev + 1);
和setCount2(count2 + 1);
,而processInc1
、processInc2
基本上是return inc1
和 inc2
分别在组件第一次渲染时通过 useState。
我从这里 https://reactjs.org/docs/hooks-faq.html#why-am-i-seeing-stale-props-or-state-inside-my-function 了解到它与闭包有关,但鉴于上面的示例,我无法理解为什么 inc2
和 processInc1
有效但无效processInc2
?
这里是上面的link到codesandbox
https://codesandbox.io/s/eloquent-morse-37xyoy?file=/src/App.js
你走在正确的道路上。正如你所说,这个问题实际上是由闭包的使用引起的。那么让我们首先向您展示闭包的一个很好的定义:
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.
文档:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
这意味着当您 return 在 useState 中关闭 inc2 时,您还创建了在该特定时间使用的变量的副本(即初始渲染)由那个词法环境(这包括 count2 值)。这就是为什么 processInc2 将保持相同的旧 count2 值。
同时 processInc1 函数将正常工作,因为通过在 useState 中使用回调,您始终可以获得当前count1 状态的值。
最后,inc2 起作用是因为您在单击按钮时直接调用它,因此 count2 值会在那一刻得到评估您调用它(因此它很可能具有当前值)。
这里一个非常重要的细节是 inc1
和 inc2
每次 App
组件渲染时都是 re-defined。
function App() {
// ...
const inc1 = () => {
console.log("debug inc:", count1);
setCount1((prev) => prev + 1);
};
// ...
}
然后将这 2 个函数存储在一个状态中,该状态永远不会改变。
const [processInc1] = useState(() => {
console.log("debug longProcessBeforeInc:", count1);
// Run some long process here
return inc1;
});
这将导致 processInc1
和 processInc2
指向 inc1
和 inc2
的第一个定义(在第一次渲染时创建)。
count1
和 count2
在函数的第一个版本中从不更新的原因是变量从不 re-assigned。这是设计使然。
count1
和 count2
在未来的渲染中改变的唯一原因是因为 useState()
将 return 新值。收到此新值后 inc1
和 inc2
为 re-defined.
processInc1
和 processInc2
被拉出包含 inc1
和 inc2
的第一个定义的 React 状态,因此 count1
的用法这些函数中的 count2
将引用 count1
和 count2
.
当你在 inc2
中执行 setCount2(count2 + 1)
并通过 processInc2
调用它时, count2
的值仍然是 0
并且永远不会改变。这是因为 processInc2
指的是 inc2
的第一个定义,而不是当前定义。
setCount1((prev) => prev + 1)
由于不同的函数签名而起作用。其中 inc2
将静态值 (0 + 1
) 传递给 setter,inc1
传递一个转换(作为回调)。当您将函数传递给 setCount1
时,React 将使用当前状态作为唯一参数调用该函数。然后 return 值用作新状态。所以尽管 processInc1
仍然使用 inc1
的第一个定义。它总是相关的,因为它描述了必须进行的转换而不是必须设置的值。
请注意,出于上述原因,当通过 processInc1
调用时,console.log("debug inc:", count1);
将继续记录 0
。