0

所以我有一个组件,当它挂载时,我会查看通过自定义钩子(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,
  };
}
4

2 回答 2

0

我认为这里的答案是 useRef 每次都返回相同的引用,即使在组件的后续渲染中也是如此。所以选择器再次运行,但这可能实际上并没有对我的参考做任何事情。但是随后组件重新渲染,useRef 再次被调用,返回与闭包中相同的ref,然后 useSelector 调用(来自组件渲染)再次发生,将更新的状态值写入 ref。

于 2020-05-16T22:02:26.733 回答
0

快速回顾一下,useAPI 中的代码本身有一个无限循环需要修复。回到问题上来,useEffect 必须启动一个监听工作者,以便它可以更新状态。它会像下面这样

useEffect(() => {
    const fetchData = async () => {
      const result = await apollForStatus();
      setData(result.data);
    };

    fetchData();
  }, [pollForStatus, status]);
于 2020-05-16T06:26:12.870 回答