为什么 useEffect 中的 useState 在 React 中不起作用?
Why is useState within useEffect not working in React?
我使用 useEffect()
获取 Firestore 快照并并行我想计算一个值:
const [counter, setCounter] = useState({ mona: 0, phil: 0 });
useEffect(() => {
onSnapshot(q, (snapshop) => {
setTasks(
snapshop.docs.map((doc) => {
if (doc.data().wer === "Mona") {
console.log("Mona + 1"); // This get's executed as expected (e.g. 3 times)
setCounter({ ...counter, mona: counter.mona + 1 });
}
if (doc.data().wer === "Phil") {
console.log("Phil + 1"); // This get's executed as expected (e.g. 6 times)
setCounter({ ...counter, phil: counter.phil + 1 });
}
return {
...doc.data(),
id: doc.id,
timestamp: doc.data().timestamp?.toDate().getTime(),
};
})
);
setLoading(false);
});
}, []);
useEffect(() => {
console.log({ counter }); //this get's executed only 2 times.
}, [counter]);
当 map()
中的 console.log()
正确执行时,为什么 setCounter 没有执行或更新计数器正确?
console.log({ counter });
顺便说一句,除了:
{counter: {mona: 0, phil: 0}}
{counter: {mona: 0, phil: 1}}
您传递给 useEffect
的函数关闭 counter
变量。
当您调用 setCounter
时,它会更新商店中的 counter
,并且挂钩会重新呈现。效果挂钩 不再 运行 因为 none 的依赖项([]
- 有 none)已经改变。
下一次触发由onSnapshot
设置的事件处理程序时,它使用与上次counter
相同的值。这意味着 counter.phil
仍然是 0
在 effect hook 中。您将 1
添加到 0
再次 并调用 setCounter
但此值与之前的值相同。
由于这次counter
没有改变,所以依赖于counter
值的第二个效果挂钩不会被触发。
将函数传递给setCounter
以获取最新值而不是原始关闭值:
setCounter((latestCounter) => { ...latestCounter, phil: latestCounter.phil + 1 });
React 有时会批量更新状态。这意味着您对 setCounter
的所有调用只会触发一种效果。
此外,函数内部 counter
的值也在函数结束时更新,因此您正在丢失更新。
你应该做什么:
首先传递一个回调给setCounter
而不是使用counter
的值。所以改变:
setCounter({ mona: counter.mona, phil: counter.phil + 1 });
至:
setCounter(counter => ({ mona: counter.mona, phil: counter.phil + 1 }));
要强制多次调用 useEffect,您必须使用 ReactDOM.flushSync
:
选择退出批量更新
import { flushSync } from 'react-dom';
// ...
flushSync(() => setCounter(counter => ({ mona: counter.mona, phil: counter.phil + 1 })));
通过这种方式,您的 useEffect
应该在每次更改计数器时被调用。显然,这比批处理更新效率低。
由于每次调用 onSnapshot 时您都需要重新加载整个数据集,而不是简单地修改当前值。
在这种情况下,您可以这样做:
const newCounter = { mona: 0, phil: 0};
snapshop.docs.map((doc) => {
if (doc.data().wer === "Mona") {
console.log("Mona + 1"); // This get's executed as expected (e.g. 3 times)
newCounter.mona += 1;
}
if (doc.data().wer === "Phil") {
console.log("Phil + 1"); // This get's executed as expected (e.g. 6 times)
newCounter.phil += 1;
}
// ...
});
setCounter(newCounter);
因此您只需计算结果并在循环外调用 setCounter
一次,并使用最终计数。在这种情况下,您不需要读取旧状态,因为您是从头开始重新计算的。
你 可以 保留旧代码并在循环外添加一个 setCounter({mona: 0, phil: 0})
,但我相信它的效率低于在反应钩子之外计算值并且只调用 setCounter
一次。
我使用 useEffect()
获取 Firestore 快照并并行我想计算一个值:
const [counter, setCounter] = useState({ mona: 0, phil: 0 });
useEffect(() => {
onSnapshot(q, (snapshop) => {
setTasks(
snapshop.docs.map((doc) => {
if (doc.data().wer === "Mona") {
console.log("Mona + 1"); // This get's executed as expected (e.g. 3 times)
setCounter({ ...counter, mona: counter.mona + 1 });
}
if (doc.data().wer === "Phil") {
console.log("Phil + 1"); // This get's executed as expected (e.g. 6 times)
setCounter({ ...counter, phil: counter.phil + 1 });
}
return {
...doc.data(),
id: doc.id,
timestamp: doc.data().timestamp?.toDate().getTime(),
};
})
);
setLoading(false);
});
}, []);
useEffect(() => {
console.log({ counter }); //this get's executed only 2 times.
}, [counter]);
当 map()
中的 console.log()
正确执行时,为什么 setCounter 没有执行或更新计数器正确?
console.log({ counter });
顺便说一句,除了:
{counter: {mona: 0, phil: 0}}
{counter: {mona: 0, phil: 1}}
您传递给 useEffect
的函数关闭 counter
变量。
当您调用 setCounter
时,它会更新商店中的 counter
,并且挂钩会重新呈现。效果挂钩 不再 运行 因为 none 的依赖项([]
- 有 none)已经改变。
下一次触发由onSnapshot
设置的事件处理程序时,它使用与上次counter
相同的值。这意味着 counter.phil
仍然是 0
在 effect hook 中。您将 1
添加到 0
再次 并调用 setCounter
但此值与之前的值相同。
由于这次counter
没有改变,所以依赖于counter
值的第二个效果挂钩不会被触发。
将函数传递给setCounter
以获取最新值而不是原始关闭值:
setCounter((latestCounter) => { ...latestCounter, phil: latestCounter.phil + 1 });
React 有时会批量更新状态。这意味着您对 setCounter
的所有调用只会触发一种效果。
此外,函数内部 counter
的值也在函数结束时更新,因此您正在丢失更新。
你应该做什么:
首先传递一个回调给
setCounter
而不是使用counter
的值。所以改变:setCounter({ mona: counter.mona, phil: counter.phil + 1 });
至:
setCounter(counter => ({ mona: counter.mona, phil: counter.phil + 1 }));
要强制多次调用 useEffect,您必须使用
选择退出批量更新ReactDOM.flushSync
:import { flushSync } from 'react-dom'; // ... flushSync(() => setCounter(counter => ({ mona: counter.mona, phil: counter.phil + 1 })));
通过这种方式,您的
useEffect
应该在每次更改计数器时被调用。显然,这比批处理更新效率低。
由于每次调用 onSnapshot 时您都需要重新加载整个数据集,而不是简单地修改当前值。
在这种情况下,您可以这样做:
const newCounter = { mona: 0, phil: 0};
snapshop.docs.map((doc) => {
if (doc.data().wer === "Mona") {
console.log("Mona + 1"); // This get's executed as expected (e.g. 3 times)
newCounter.mona += 1;
}
if (doc.data().wer === "Phil") {
console.log("Phil + 1"); // This get's executed as expected (e.g. 6 times)
newCounter.phil += 1;
}
// ...
});
setCounter(newCounter);
因此您只需计算结果并在循环外调用 setCounter
一次,并使用最终计数。在这种情况下,您不需要读取旧状态,因为您是从头开始重新计算的。
你 可以 保留旧代码并在循环外添加一个 setCounter({mona: 0, phil: 0})
,但我相信它的效率低于在反应钩子之外计算值并且只调用 setCounter
一次。