我玩了反应钩子,遇到了一个我不明白的问题。
代码在这里https://codesandbox.io/embed/hnrch-hooks-issue-dfk0t 该示例包含2个简单的组件:
应用组件
const App = () => {
const [num, setNum] = useState(0);
const [str, setStr] = useState();
const inc = () => {
setNum(num + 1);
setStr(num >= 5 ? ">= 5" : "< 5");
console.log(num);
};
const button = <button onClick={inc}>++</button>;
console.log("parent rerender", num);
return (
<div className="App">
<h1>App</h1>
<Child str={str}>{button}</Child>
</div>
);
};
子组件
const Child = React.memo(
({ str, children }) => {
console.log("child rerender");
return (
<div>
<>
<h2>Functional Child</h2>
<p>{str}</p>
{children}
</>
</div>
);
}
(prev, props) => {
return prev.str === props.str;
}
);
因此,我将孩子包裹在React.memo
中,并且只有在str
不同的情况下,才应重新渲染孩子。但也有孩子,并且传递了一个按钮,该按钮正在增加父(App)内部的计数器。
问题是:计数器设置为1后,计数器将停止递增。有人可以向我解释此问题以及避免那些错误我需要了解什么吗?
答案 0 :(得分:1)
这是一个“关闭问题”。
这是App
的第一次渲染时间,inc
函数是第一次创建的:(我们称之为inc#1
)
const inc = () => {
setNum(num + 1);
setStr(num >= 5 ? ">= 5" : "< 5");
console.log(num);
};
在inc#1
范围内,num
当前为0
。然后将该函数传递到button
,然后传递给Child
。
到目前为止一切都很好。现在按下按钮,inc#1
被调用,这意味着
setNum(num + 1);
发生num === 0
的位置。 App
被重新渲染,但是 Child
不是。条件是如果prev.str === props.str
,则我们不会再次渲染Child。
我们现在处于App
的第二次渲染中,但是Child
仍然拥有inc#1
实例,其中num
是0
。
您知道问题出在哪里吗?您仍将调用该函数,但是inc
现在已过时。
您可以通过多种方式解决此问题。您可以确保Child
始终具有更新的道具。
或者您可以将回调传递给setState
以获取当前值(而不是存在于闭包范围内的陈旧值)。这也是一个选择:
const inc = () => {
setNum((currentNum) => currentNum + 1);
};
React.useEffect(() => {
setStr(num >= 5 ? ">= 5" : "< 5");
}, [num])
答案 1 :(得分:1)
这里有几件事。
setState
的功能形式: setNum(num => num + 1);
setState
是异步的,因此当您尝试setStr
时,num值尚未更新。甚至,在您的特定情况下,inc
将从状态中关闭(即创建一个闭合)num
的值,因此在该函数内部,它将始终具有其初始值-0
。
要解决此问题,您需要在num
更改时使用效果钩子来更新字符串:useEffect(() => {
setStr(num >= 5 ? ">= 5" : "< 5");
}, [num]) // Track the 'num' var here
答案 2 :(得分:0)
解决该错误的一种方法是将inc
函数更改为此:
const inc = () => {
setNum(n => {
const newNum = n + 1;
setStr(newNum >= 5 ? ">= 5" : "< 5");
return newNum;
});
};
请注意,setState现在传递了一个回调函数,该函数接收旧值并返回新值。这样,“关闭”问题就解决了。