React useState不更新值 [英] React useState does not update value
问题描述
我有点困惑,为什么这个组件没有按预期工作:
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1); // This effect depends on the `count` state
}, 1000);
return () => clearInterval(id);
}, []); // 🔴 Bug: `count` is not specified as a dependency
return <h1>{count}</h1>;
}
但如下所示的重写有效:
function Counter() {
const [count, setCount] = useState(0);
let c = count;
useEffect(() => {
const id = setInterval(() => {
setCount(c++);
}, 1000);
return () => clearInterval(id);
}, []);
return <h1>{count}</h1>;
}
Reaction文档显示:
问题在于,在setInterval回调中,count的值没有更改,因为我们已经创建了一个闭包,将count的值设置为0,就像运行效果回调时一样。此回调每秒钟调用一次
setCount(0 + 1)
,因此计数永远不会超过1。
但这个解释说不通。那么,为什么第一个代码不能正确更新,而第二个代码可以呢?
(也可以声明为let [count, setCount] = useState(0)
,然后使用setCount(count++)
也可以)。
推荐答案
为什么看起来不起作用?
有几个提示可以帮助您了解正在发生的事情。
count
为const
,因此它的作用域永远不会改变。它令人困惑,因为它在调用setCount
时看起来正在更改,但它永远不会更改,只是再次调用该组件并创建一个新的count
变量。
在回调中使用count
时,闭包捕获变量,并且count
保持可用,即使组件函数已执行完毕。同样,它与useEffect
混淆,因为它看起来像是在每个呈现周期创建回调,捕获最新的count
值,但实际情况并非如此。
为清楚起见,让我们在每次创建变量时为它们添加一个后缀,并查看发生了什么。
在挂载时
function Counter() {
const [count_0, setCount_0] = useState(0);
useEffect(
// This is defined and will be called after the component is mounted.
() => {
const id_0 = setInterval(() => {
setCount_0(count_0 + 1);
}, 1000);
return () => clearInterval(id_0);
},
[]);
return <h1>{count_0}</h1>;
}
一秒后
function Counter() {
const [count_1, setCount_1] = useState(0);
useEffect(
// completely ignored by useEffect since it's a mount
// effect, not an update.
() => {
const id_0 = setInterval(() => {
// setInterval still has the old callback in
// memory, so it's like it was still using
// count_0 even though we've created new variables and callbacks.
setCount_0(count_0 + 1);
}, 1000);
return () => clearInterval(id_0);
},
[]);
return <h1>{count_0}</h1>;
}
为什么与let c
一起使用?
let
允许重新分配给c
,这意味着当它被我们的useEffect
和setInterval
闭包捕获时,它仍然可以被使用,就像它存在一样,但它仍然是第一个定义的。
在挂载时
function Counter() {
const [count_0, setCount_0] = useState(0);
let c_0 = count_0;
// c_0 is captured once here
useEffect(
// Defined each render, only the first callback
// defined is kept and called once.
() => {
const id_0 = setInterval(
// Defined once, called each second.
() => setCount_0(c_0++),
1000
);
return () => clearInterval(id_0);
},
[]
);
return <h1>{count_0}</h1>;
}
一秒后
function Counter() {
const [count_1, setCount_1] = useState(0);
let c_1 = count_1;
// even if c_1 was used in the new callback passed
// to useEffect, the whole callback is ignored.
useEffect(
// Defined again, but ignored completely by useEffect.
// In memory, this is the callback that useEffect has:
() => {
const id_0 = setInterval(
// In memory, c_0 is still used and reassign a new value.
() => setCount_0(c_0++),
1000
);
return () => clearInterval(id_0);
},
[]
);
return <h1>{count_1}</h1>;
}
挂钩的最佳实践
由于很容易与所有回调和计时混淆,并且为了避免任何意外的副作用,最好使用函数式更新器状态设置器参数。
// ❌ Avoid using the captured count.
setCount(count + 1)
// ✅ Use the latest state with the updater function.
setCount(currCount => currCount + 1)
在代码中:
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
// I chose a different name to make it clear that we're
// not using the `count` variable.
const id = setInterval(() => setCount(currCount => currCount + 1), 1000);
return () => clearInterval(id);
}, []);
return <h1>{count}</h1>;
}
还有很多事情要做,需要对该语言进行更多的解释,才能准确地解释它是如何工作的,以及为什么它是这样工作的,尽管为了简单起见,我将重点放在了您的示例上。
这篇关于React useState不更新值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!