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

时间:2019-11-18 06:55:05

标签: reactjs react-hooks

给出任务列表:

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}},所以我可以在本地更新它。

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

2 个答案:

答案 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)));

因此,它不会轮询组件是否不再处于活动状态