所以我有一个组件,当它挂载时,我会查看通过自定义钩子(useStatus)向我公开的“进程状态”值。如果该状态是“正在加载”,那么我通过调用另一个钩子 (useAPI) 中的函数来开始轮询 API 的过程。每次我“轮询”时,我都会更新 redux 状态,其想法是,当值更改指示状态为“已完成”时,我会停止轮询。
这不起作用,因为我在 setTimeout 闭包中使用的值是“陈旧的”。我想我理解这是因为当调用 setTimeout 时,它正在创建一个闭包,并且该闭包无权访问“更新的”状态值。
我不明白的是,如果是这种情况,为什么 statusRef.current 值会更新?通过日志记录和调试,我确定由于useSelector 代码中的 checkForUpdates 函数,当状态发生变化时,会再次调用原始选择器(位于 setTimeout 闭包中) 。
但我原以为对 useSelector 的原始调用会返回一个字符串(而不是对象),该字符串存储在 statusRef.current 属性中。而且我无法理解选择器的后续运行如何能够“重新分配”该值。
更新 我认为这里的答案是 useRef 每次都返回相同的引用,即使在组件的后续渲染中也是如此。所以选择器再次运行,但这可能实际上并没有对我的参考做任何事情。但是随后组件重新渲染,useRef 再次被调用,返回与闭包中相同的 ref,然后 useSelector 调用(来自组件渲染)再次发生,将更新的状态值写入 ref。
我创建了一个 create-react-app 项目来演示这里的行为
代码基本上是这样的:
// App.js
const App = () => {
const dispatch = useDispatch();
const { status } = useStatus();
const { pollForStatus } = useAPI();
useEffect(() => {
if(status === 'loading') {
pollForStatus();
}
}, [pollForStatus, status]);
return (
<div className="app">
<div>
Status is <span>{status}</span>.
</div>
<button
onClick={() => {
dispatch(({
type: "SET_STATUS",
status: "completed",
}))
}}>
Click to update status.
</button>
</div>
);
}
// useStatus.js
export default () => {
const statusRef = useRef();
statusRef.current = useSelector(state => state.status);
return {
status: statusRef.current,
statusRef,
};
}
// useAPI.js
export default () => {
const { status, statusRef, } = useStatus();
const pollForStatus = async () => {
const poll = async (resolve, reject) => {
await apiRequestThatUpdatesState();
// here `status` is the same value is was when the setTimeout closure was created
// but `statusRef.current changes to the "updated value" eventually
if (status === 'loading') {
setTimeout(poll, 5000, resolve, reject);
}
}
return new Promise(poll);
};
return {
pollForStatus,
};
}