React Hook useEffect存在缺失的依赖项。要么包括它们,要么删除依赖项数组。

10

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

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

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

这两个函数是通过react-redux的connect方法传递给组件的,整个组件看起来像这样:

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

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

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

1
https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies - Paul
3个回答

14

简要概述:

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

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

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


您可能会遇到一个问题,即useEffect的回调函数运行的次数比预期的多。请考虑以下示例:

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

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

useEffect的回调函数中使用foo,会将它添加到依赖项中。但是每次组件Bla重新渲染时,都会创建一个新的函数并改变foo的值。这将触发useEffect的回调函数。

可以使用useCallback钩子来解决此问题:

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

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

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


@nick 请记住,如果getWebsites()loadUserSettings()被传递为函数表达式而不是静态定义,则这将导致useEffect()在每次Wrapper渲染时触发。请参考我的答案,了解如何处理这种情况。 - Patrick Roberts
@PatrickRoberts 我更新了我的回答,我们也可以像我做的那样将它们进行记忆化,这样正确吗? - Ramesh Reddy
@Ramesh useCallback()需要两个参数,而不是一个。你不能使用useCallback()来绕过这个问题,它必须被初始化为状态。 - Patrick Roberts
@PatrickRoberts我们可以将类似[props.getWebsites]的数组作为第二个参数传递吗? - Ramesh Reddy
@Ramesh 不行,因为这和直接使用 getWebsites() 没有什么区别。我认为你最初的回答已经很好了,我的回答已经解决了遇到未记忆化函数时该怎么做的问题。 - Patrick Roberts
@nick 确保传递的函数已正确进行了记忆化,如果没有,请遵循Patrick的答案。 - Ramesh Reddy

2

根据我的经验,不典型的做法是通过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()函数在其返回值中记录了调度函数的值,因此从技术上讲,在每个useCallback()中将[]作为deps传递是安全的,但我认为将调度函数指定为依赖项有助于通过明确传达作者的意图来提高清晰度,并且没有传递它们的缺点。

Ramesh的回答已足够应对这种情况。


如果你发现自己卡在第一种情况中,则可以像这样将props初始化到组件的状态中作为最后的选择。

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

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

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

@Luze,就个人而言,我不同意这是一个“伟大的模式”的说法。如果通过其他方式进行记忆化过于麻烦,我更喜欢将初始化props到状态中作为最后的手段。但通常情况下并不需要这样做。 - Patrick Roberts
1
@PatrickRoberts 我认为在父组件中使用useCallback钩子并传递这些函数是一个不错的方法,需要注意必要的依赖项。 - Ramesh Reddy
@Ramesh,我在我的答案中详细阐述了你的评论。 - Patrick Roberts
@PatrickRoberts 很好,我想说的是你可以跳过将 setUserRatingsetWebsites 添加到依赖项中,因为 React 会确保它们被正确地记忆。当我们不添加它们时,React 也不会显示任何警告。这是因为该函数来自于 useState 钩子而不是用户定义。尽管如此,添加它们也是可以的。 - Ramesh Reddy
@Ramesh,请查看我添加的脚注。 - Patrick Roberts
1
@PatrickRoberts 是的,我完全同意。我见过一些React项目这样做。 - Ramesh Reddy

2

您需要将 getWebsitesloadUserRatings 添加到 useEffect 的依赖项中:

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

所有在useEffect钩子之外定义的变量都需要添加,但要求它们是在调用useEffect()的组件或自定义钩子的主体中定义的,或者从参数中传递下来的。在组件或自定义钩子之外定义的变量不需要添加到依赖列表中。(记忆化)这是较少人知道的hook规则之一。
注意:(对于您的情况,这并不适用,因为您的函数是通过props传递的)您还可以将函数包装在useCallback钩子中,或者在useEffect钩子本身内部定义您需要的变量,如果您不想将其添加到useEffect的依赖项中。

1
所有在 useEffect 钩子之外定义的变量都需要被添加,但前提是它们必须在组件或自定义钩子的主体中定义,或者从参数中传递下来。在组件或自定义钩子之外定义的变量不需要被添加到依赖列表中。 - Patrick Roberts

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接