【问题标题】:question re: customHooks, setTimeout closure, and useSelector问题回复:customHooks、setTimeout 闭包和 useSelector
【发布时间】:2020-05-16 00:44:15
【问题描述】:

所以我有一个组件,当它挂载时,我会查看通过自定义挂钩 (useStatus) 向我公开的“进程状态”值。如果该状态是“正在加载”,那么我通过调用另一个钩子 (useAPI) 中的函数来开始轮询 API 的过程。每次我“轮询”时,我都在更新 redux 状态,其想法是当值发生变化时表明状态为“已完成”,我停止轮询。

这不起作用,因为我在 setTimeout 闭包中使用的值是“陈旧的”。我想我理解这是因为当调用 setTimeout 时,它正在创建一个闭包,而该闭包无权访问“更新的”状态值。

我不明白的是,如果是这种情况,为什么 statusRef.current 值会更新?通过日志记录和调试,我确定当状态因this checkForUpdates function in the useSelector code 而发生变化时,会再次调用原始选择器(位于 setTimeout 闭包中)。

但我原以为对 useSelector 的原始调用会返回一个字符串(而不是对象),该字符串存储在 statusRef.current 属性中。而且我无法理解选择器的后续运行如何能够“重新分配”该值。

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

我创建了一个 create-react-app 项目来演示 here 的行为

代码基本上是这样的:

// 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,
  };
}

【问题讨论】:

    标签: reactjs react-redux react-hooks


    【解决方案1】:

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

    useEffect(() => {
        const fetchData = async () => {
          const result = await apollForStatus();
          setData(result.data);
        };
    
        fetchData();
      }, [pollForStatus, status]);
    

    【讨论】:

    • 感谢您的回复。 react-redux 代码确实订阅了状态事件,当状态发生变化时,它似乎再次运行我的选择器(OP 中提到的 checkForUpdates 函数)。但我不明白再次运行选择器如何更改变量statusRef.current。在 redux 代码中,它将值分配给 latestSelectedState.current,但我在代码中看不到任何将 lastSelectedState.current 分配给 statusRef.current 的内容,我试图了解这一切是如何发生的。
    • 我认为组件的后续渲染(由于状态更改而发生)可能再次调用 useStatus() 。也许这最终会更新闭包中使用的 same 引用。我曾以为每次调用 useStatus(然后是 useRef)都会创建一个新的 ref,但也许我在那里不正确。
    【解决方案2】:

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

    【讨论】:

      猜你喜欢
      • 2011-07-25
      • 2012-03-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-25
      • 1970-01-01
      • 2023-01-12
      • 2016-10-03
      相关资源
      最近更新 更多