![](/img/trans.png)
[英]React 16.7 TypeError: (0 , _react.useState) is not a function
[英]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
基本上在组件第一次渲染时分别通过 useState 返回inc1
和inc2
。
我从这里了解到https://reactjs.org/docs/hooks-faq.html#why-am-i-seeing-stale-props-or-state-inside-my-function它与关闭有关,但是鉴于上面的示例,我无法理解为什么inc2
和processInc1
有效但processInc2
无效?
这是上述https://codesandbox.io/s/eloquent-morse-37xyoy?file=/src/App.js的codesandbox
链接
你走在正确的道路上。 正如您所说,这个问题实际上是由使用闭包引起的。 因此,让我们首先向您展示一个很好的闭包定义:
闭包是函数和声明该函数的词法环境的组合。 该环境由创建闭包时在范围内的任何局部变量组成。
文档: https ://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
这意味着当您在 useState 中返回闭包inc2时,您还创建了该词法环境在特定时间(即初始呈现)使用的变量的副本(这包括 count2 值)。 这就是为什么processInc2将保持相同的旧 count2 值。
同时processInc1函数将正常工作,因为通过在 useState 中使用回调,您始终可以获取 count1 状态的当前值。
最后, inc2有效,因为您在单击按钮时直接调用它,因此在您调用它的那一刻计算 count2 值(因此它很可能具有当前值)。
这里一个非常重要的细节是,每次App
组件渲染时都会重新定义inc1
和inc2
。
function App() {
// ...
const inc1 = () => {
console.log("debug inc:", count1);
setCount1((prev) => prev + 1);
};
// ...
}
然后,您将这两个函数存储在一个永远不会改变的状态中。
const [processInc1] = useState(() => {
console.log("debug longProcessBeforeInc:", count1);
// Run some long process here
return inc1;
});
这将导致processInc1
和processInc2
指向inc1
和inc2
的第一个定义(在第一次渲染时创建)。
在函数的第一个版本中, count1
和count2
永远不会更新的原因是因为永远不会重新分配变量。 这是设计使然。
count1
和count2
在未来渲染中发生变化的唯一原因是useState()
将返回新值。 收到这个新值后, inc1
和inc2
被重新定义。
然后processInc1
和processInc2
被拉出包含inc1
和inc2
的第一个定义的 React 状态,因此在这些函数中使用count1
和count2
将引用count1
和count2
的第一个值。
当您在 inc2 中执行inc2
setCount2(count2 + 1)
并通过processInc2
调用它时, count2
的值仍然是0
并且永远不会改变。 这是因为processInc2
指的是inc2
的第一个定义,而不是当前定义。
setCount1((prev) => prev + 1)
由于不同的函数签名而起作用。 其中inc2
将静态值 ( 0 + 1
) 传递给 setter,而inc1
则传递转换(作为回调)。 当你将一个函数传递给setCount1
时,React 会以当前状态作为唯一参数调用该函数。 然后将返回值用作新状态。 因此,尽管processInc1
仍然使用inc1
的第一个定义。 它总是相关的,因为它描述了必须进行的转换,而不是必须设置的值。
请注意console.log("debug inc:", count1);
由于上述原因,通过processInc1
调用时将保持记录0
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.