【问题标题】:React hooks: How to read & update state in hooks without infinite loops with react-hooks/exhaustive-deps ruleReact hooks:如何使用 react-hooks/exhaustive-deps 规则在没有无限循环的情况下读取和更新钩子中的状态
【发布时间】:2019-08-22 10:26:15
【问题描述】:

当状态处于钩子中时,它可能会变得陈旧并泄漏内存:

function App() {
  const [greeting, setGreeting] = useState("hello");

  const cb = useCallback(() => {
    alert("greeting is " + greeting);
  }, []);

  return (
    <div className="App">
      <button onClick={() => cb()}>Click me</button>
      <p>
        Click the button above, and now update the greeting by clicking the one
        below:
      </p>
      <button onClick={() => setGreeting("bye")}>
        Update greeting
      </button>
      <p>Greeting is: {greeting}</p>
      <p>
        Now click the first button again and see that the callback still has the
        old state.
      </p>
    </div>
  );
}

演示:https://codesandbox.io/s/react-hook-stale-datamem-leak-demo-9pchk

这样做的问题是,如果我们按照 Facebook 的建议始终列出所有依赖项,并确保我们没有陈旧数据或内存泄漏(如上面的例子):

const [state, setState] = useState({
  number: 0
});

const fetchRandomNumber = useCallback(async () => {
  if (state.number !== 5) {
    const res = await fetch('randomNumber');
    setState(v => ({ ...v, number: res.number }));
  }
}, [setState, state.number]);

useEffect(() => {
  fetchRandomNumber();
}, [fetchRandomNumber]);

由于 Facebook 说我们应该将 fetchRandomNumber 列为依赖项(react-hooks/exhaustive-deps ESLint 规则),我们必须使用 useCallback 来维护引用,但它在每次调用时重新生成,因为它取决于 state.number更新它

这是一个人为的例子,但我在获取数据时遇到过很多次。在这种情况下是否有解决方法或 Facebook 有问题?

【问题讨论】:

标签: javascript reactjs react-hooks


【解决方案1】:

使用状态设置器的函数形式:

const fetchData = useCallback(async () => {
  const res = await fetch(`url?page=${page}`);
  setData((data) => ([...data, ...res.data]));
  setPage((page) => page + 1);
}, [setData, setPage]);

现在您不需要数据和页面作为您的部门


您也可以使用 ref 仅在 mount 上运行效果:

  const mounted = useRef(false);

  useEffect(() => {
    if(!mounted.current) {
      fetchSomething();
      mounted.current = true;
    }

    return () => { mounted.current = false }
  }, [fetchSomething]);

还有

const fetchSomething = useCallback(async () => {
  ...
}, [setData, setPage, data, page]);

【讨论】:

  • 这适用于设置,但不适用于读取状态(page 仍应传递给 dep)。如果状态是非原始状态并且未添加到 deps,如果状态更新为新对象或数组(并且也是内存泄漏),它将引用错误的内容
  • 我用一个使用 ref 的示例更新了我的答案,这将防止无限循环。这仅适用于安装盒
  • 嗯,这是一个有趣的想法,谢谢,我想它也可以提取到可重复使用的钩子中。在这种情况下,我倾向于 facebook 是错误的,但是如果我们必须出于其他原因使用钩子并且不希望它被 useEffect 一次又一次地调用,这仍然很有用
【解决方案2】:

fetchSomething 不是这里的依赖项。您不想重新触发效果,只在组件安装时触发一次。这就是useEffect(() =&gt; ..., []) 的用途。

【讨论】:

  • 我已经更清楚地重新表述了这个问题 - Facebook 添加了一个名为 react-hooks/exhaustive-deps 的 ESLint 规则,您无法全局关闭该规则。他们认为任何不属于组件外部的变量/函数/状态/道具都应该始终列在 deps 中
猜你喜欢
  • 2020-11-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-11
  • 2020-06-09
  • 2022-08-18
相关资源
最近更新 更多