给出任务列表:
const [tasks, setTasks] = useState([])
我想用setTasks(...tasks, aNewTask)
在用户输入中添加一个任务,然后异步更新该任务的结果:
while (true) {
taskStatus = await getTaskStatus()
setTasks(tasks.map(t => t.id == taskStatus.id ? taskStatus : t))
}
看起来在逻辑上是正确的。但这是行不通的。任务被添加到列表中,然后被删除。 tasks
不会被更新,因此在我第一次致电setTasks
之前,对其进行查询会生成原始列表。在仍然使用相同模式的情况下,我看到的最佳解决方法是将useState
包装在自定义钩子中,并将promise解析为设置值函数的一部分,但是即使如此,我仍然需要tasks
是{ {1}},所以我可以在本地更新它。
有没有一种更清洁的方法,仍然使用异步逻辑?
答案 0 :(得分:1)
如果您具有在异步操作后设置状态的效果,则应在设置该状态之前检查组件是否仍已安装。
这里是一个示例,该示例在设置状态之前进行检查,并在卸载组件时退出无限循环。该效果没有依赖性,因此仅在首次渲染后运行。这些任务与效果无关,因为我将回调传递给setTasks。
const { useRef, useEffect, useState } = React;
//helper to check if component is mounted so you won't
// try to set state of an unmounted component
// comes from https://github.com/jmlweb/isMounted/blob/master/index.js
const useIsMounted = () => {
const isMounted = useRef(false);
useEffect(() => {
isMounted.current = true;
return () => (isMounted.current = false);
}, []);
return isMounted;
};
//returns current date after waiting for a second
function getTasksStatus() {
return new Promise(r =>
setTimeout(() => r(Date.now(), 1000))
);
}
function App() {
const [tasks, setTasks] = useState(
Math.floor(Date.now() / 1000)
);
//from the helper to check if component is still mounted
const isMounted = useIsMounted();
useEffect(() => {
//babel of Stack overflow is ancient and doesn't know what
// to do with async so I create a recursive instead
function polling() {
getTasksStatus().then(newTasks => {
//only do something if component is still mounted
if (isMounted.current) {
//pass callback to setTasks so effect doesn't depend on tasks
setTasks(currentTasks =>
Math.floor(newTasks / 1000)
);
//call recursively
polling();
}
});
}
//the async version with "infinite" loop looks like this
// async function polling() {
// //exit loop if component is unmounted
// while (isMounted.current) {
// const newTasks = await getTasksStatus();
// isMounted.current && //make sure component is still mounted
// //pass callback to setTasks so effect doesn't depend on tasks
// setTasks(currentTasks =>
// Math.floor(newTasks / 1000)
// );
// }
// }
polling();
}, [isMounted]);
return <div>{tasks}</div>;
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
答案 1 :(得分:0)
在循环内执行setState
的问题是它只会触发一次。为了使其能够“轮询”,您可以使用超时功能将其包装起来:
let timeout = null; //outside class
...
while (true) {
timeout = setTimeout(() => {
taskStatus = await getTaskStatus()
setTasks(tasks.map(t => t.id == taskStatus.id ? taskStatus : t))
}, 1000);
}
1000
是运行设置的任务/轮询的毫秒数。
请确保您清除卸载时的超时,如下所示:
useEffect(() => () => clearTimeout(timeout)));
因此,它不会轮询组件是否不再处于活动状态