【问题标题】:React Hook useEffect has missing dependencies. Either include them or remove the dependency arrayReact Hook useEffect 缺少依赖项。包括它们或删除依赖数组
【发布时间】:2020-07-19 22:31:22
【问题描述】:

我正在制作一个 React 应用程序,但我收到了这个警告。我正在尝试在组件挂载时进行两次 API 调用:

useEffect(() => {
  getWebsites();
  loadUserRatings();
}, []);

这就是我有一个空数组的原因,因为我只想在组件安装时调用它一次。但我仍然收到警告,我该如何解决?

这两个函数通过 react redux 中的 connect 传递给组件,整个组件如下所示:

const Wrapper = (props) => {
  const { getWebsites, loadUserRatings } = props;

  useEffect(() => {
    getWebsites();
    loadUserRatings();
  }, []);

  return (
    <>
      <Header />
      <Websites />
      <Sync />
    </>
  );
};

【问题讨论】:

标签: reactjs


【解决方案1】:

TL;DR

您应该将getWebsitesloadUserRatings 添加到依赖项数组中。

useEffect(() => {
  getWebsites();
  loadUserRatings();
}, [getWebsites, loadUserRatings]);

React 要求您将变量(其值可能更改)添加到依赖数组,以便回调可以使用这些变量的更新值。


您可能会遇到useEffect 的回调运行次数超出预期的问题。考虑下面的例子:

const Bla = (props) => {
 const foo = () => {
  console.log('foo')
 }

 useEffect(() => {
  foo();
 }, [foo])
 
 return <span>bla</span>
}

由于foouseEffect 的回调中使用,它被添加到依赖项中。但是每次组件Bla 呈现时,都会创建一个新函数并且foo 会发生变化。这将触发useEffect 的回调。

这可以使用钩子useCallback修复:

const foo = useCallback(() => {
  console.log('foo');
}, []);

现在,当Bla 重新渲染时,仍会创建一个新函数,但useCallback 将确保foo 不会更改(记忆),这有助于防止useEffect 的回调再次运行。

注意:如果foo 中使用的变量随时间变化,则应将它们添加到useCallback 的依赖项数组中,以便函数使用更新后的值。

【讨论】:

  • @nick 请记住,如果 getWebsites()loadUserSettings() 作为函数表达式而不是静态定义传递下去,那么这将导致 useEffect() 在每次 Wrapper 呈现时触发。请参阅我的答案以了解如何处理这种情况。
  • @PatrickRoberts 我更新了我的答案,我们也可以像我一样记住它们,对吗?
  • @Ramesh useCallback() 接受两个参数,而不是一个。你不能使用useCallback() 来解决这个问题,它必须被初始化为状态。
  • @PatrickRoberts 我们可以传递像[props.getWebsites] 这样的数组作为第二个参数吗?
  • @Ramesh no 因为这与直接使用getWebsites() 没有什么不同。我认为您最初的答案很好,我的答案已经解决了遇到未记忆的功能时该怎么办。
【解决方案2】:

你必须将getWebsitesloadUserRatings添加到useEffect的依赖中:

useEffect(() => {
  getWebsites();
  loadUserRatings();
}, [getWebsites, loadUserRatings]

需要添加在useEffect 钩子之外定义的所有变量,并规定它们是在组件主体或自定义钩子中定义的,从中调用useEffect() 或从参数向下传递。在组件或自定义挂钩之外定义的变量不需要添加到依赖项列表中。 (memoization) 它是鲜为人知的rules of hooks 之一。


注意:(这不适用于您的场景,因为您的函数是通过 props 传递的)您还可以将您的函数包装在 useCallback 钩子中,或者在 useEffect 钩子中定义您需要的变量,如果您不想将其添加到 useEffect 挂钩的依赖项中。

【讨论】:

  • useEffect钩子外定义的所有变量都需要添加,规定它们是在组件的主体中定义的或者自定义钩子useEffect()从参数调用或传递。在组件或自定义挂钩之外定义的变量不需要添加到依赖项列表中。
【解决方案3】:

根据我的经验,像这样通过props 传递未记忆的函数并不常见。

// don't do this

<Wrapper
  getWebsites={() => fetchJson('websites').then(setWebsites)}
  loadUserRatings={() => fetchJson('ratings').then(setUserRatings)}
/>

如果它们被正确记忆(使用像useCallback() 这样的钩子,或者通过在任何组件之外定义),那么可以安全地将它们传递给useEffect()deps,而不会产生任何行为差异。这是一个修复上述情况的示例。*

// do this

const fetchJson = (...args) => fetch(...args).then(res => res.json());

const Parent = () => {
  const [websites, setWebsites] = useState([]);
  const [userRatings, setUserRatings] = useState({});

  // useCallback creates a memoized reference
  const getWebsites = useCallback(
    () => fetchJson('websites').then(setWebsites),
    [setWebsites]
  );
  const loadUserRatings = useCallback(
    () => fetchJson('ratings').then(setUserRatings),
    [setUserRatings]
  );

  ...

  <Wrapper
    getWebsites={getWebsites}
    loadUserRatings={loadUserRatings}
  />

* useState() 在其返回值中存储调度函数,因此从技术上讲,将[] 作为deps 传递给每个useCallback() 是安全的,但我相信指定调度函数因为依赖项通过明确传达作者的意图有助于提高清晰度,并且传递它们没有任何缺点。

Ramesh's answer 足以应付这种情况。


如果你发现你被第一个场景卡住了,那么,作为最后的手段,你可以像这样将props初始化为你的组件的状态。

const Wrapper = (props) => {
  const [{ getWebsites, loadUserRatings }] = useState(props);

  useEffect(() => {
    getWebsites();
    loadUserRatings();
  }, [getWebsites, loadUserRatings]);

  return (
    <>
      <Header />
      <Websites />
      <Sync />
    </>
  );
};

【讨论】:

  • @Luze 我个人不同意这是一个“伟大的模式”的说法。如果通过其他方式记忆太不方便,我更愿意将初始化 props 状态作为最后的手段,但通常情况并非如此。
  • @PatrickRoberts 我认为在父组件中使用 useCallback 钩子和必要的依赖关系,然后传递这些函数是一个好方法。
  • @Ramesh 我在回答中详细说明了您的评论。
  • @PatrickRoberts 很好,我想说的一件事是你可以跳过 setUserRating, setWebsites 添加依赖项,因为 react 确保它们被正确记忆。当我们不添加它们时,React 也不会显示任何警告。这是因为函数来自useState 钩子而不是用户定义的。不过,也可以添加它们。
  • @PatrickRoberts 是的,我完全同意。我已经看到一些 React 项目这样做了。
猜你喜欢
  • 2021-10-29
  • 2020-05-31
  • 1970-01-01
  • 2020-11-05
  • 1970-01-01
  • 2021-05-07
  • 2021-08-24
  • 2023-04-09
  • 1970-01-01
相关资源
最近更新 更多