如何轮询列表中项目的更新



给定任务列表:

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作为集值函数的一部分来解析,但即使这样,我也需要tasksvar,这样我就可以在本地更新它。

有没有一种更干净的方法,仍然使用异步逻辑?

当您具有在异步操作之后设置状态的效果时,您应该在设置该状态之前检查组件是否仍处于安装状态。

下面是一个在设置状态之前进行检查的示例,当组件卸载时将退出无限循环。该效果没有依赖项,因此将仅在第一次渲染后运行。任务不是效果的依赖项,因为我将回调传递给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>

在循环中执行setState的问题是它只会触发一次。为了让它"轮询",你可以用超时功能包装它:

let timeout = null; //outside class
...
while (true) {
timeout = setTimeout(() => {
taskStatus = await getTaskStatus()
setTasks(tasks.map(t => t.id == taskStatus.id ? taskStatus : t))
}, 1000);
}

CCD_ 8是运行所设置的任务/轮询的毫秒数。

确保在卸载时清除超时,如下所示:

useEffect(() => () => clearTimeout(timeout)));

这样它就不会轮询组件是否不再活动

相关内容

  • 没有找到相关文章

最新更新