React Hook useEffect 缺少依赖项。包括它们或删除依赖数组

IT技术 reactjs
2021-05-24 06:41:25

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

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

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

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

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

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

  return (
    <>
      <Header />
      <Websites />
      <Sync />
    </>
  );
};
3个回答

TL; 博士

您应该将getWebsites,添加loadUserRatings到依赖项数组。

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的依赖项数组中,以便函数使用更新的值。

您必须添加getWebsitesloadUserRatings的依赖项useEffect

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

useEffect需要添加钩子外部定义的所有变量,并规定它们是在组件或自定义钩子中定义的,useEffect() 是从调用的,或从参数传递下来的。在组件或自定义钩子之外定义的变量不需要添加到依赖项列表中。记忆)这是鲜为人知的钩子规则之一


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

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

// don't do this

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

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

// 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()memoizes在其返回值的调度功能,因此从技术上讲是安全的通过[]deps每一个useCallback()在这里,但我相信,指定调度功能的依赖有助于明确地传达了作者的意图更加清晰,而且也没有缺点传球他们。

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


如果您发现自己遇到了第一种情况,那么作为最后的手段,您可以props像这样初始化组件的状态。

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

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

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