如何使用 React Hooks 进行 fetch;ESLint 强制执行`exhaustive-deps` 规则,导致无限循环

IT技术 reactjs react-redux eslint react-hooks
2021-05-27 08:38:27

总的来说,我对 React 钩子很陌生,对useSelectoruseDispatchin也很陌生react-redux,但是get当我的组件加载时,我无法执行简单的请求。我希望get只发生一次(当组件最初加载时)。我以为我知道该怎么做,但是我遇到了一个 ESLint 问题,这使我无法执行我认为是合法的代码。

我有这个钩子,我试图抽象我的状态代码:

export const useState = () => {
  const dispatch = useDispatch();
  const data = useSelector((state) => state.data);

  return {
    data: data,
    get: (props) => dispatch(actionCreators.get(props))
  };
};

在上述函数后面,有一个通过redux-sagaand发生的网络请求axios,并且已经在生产代码中运行了一段时间。到现在为止还挺好。现在我想在一个功能组件中使用它,所以我写了这个:

import * as React from 'react';
import { useState } from './my-state-file';

export default () => {
  const myState = useState();

  React.useEffect(
    () => {
      myState.get();
      return () => {};
    },
    []
  );
  return <div>hello, world</div>;
};

我期望发生的是因为我useEffect有一个空数组作为第二个参数,它只会执行一次,所以get当组件加载时会发生,就是这样。

但是,我在 Atom 中保存时运行 ESLint,每次保存时,它都会将第二个[]参数更改为[myState],其结果是:

import * as React from 'react';
import { useState } from './my-state-file';

export default () => {
  const myState = useState();

  React.useEffect(
    () => {
      myState.get();
      return () => {};
    },
    [myState]
  );
  return <div>hello, world</div>;
};

如果我加载这个组件,那么 get 会运行每个渲染,这当然与我想要发生的完全相反。在保存时没有运行 ESLint的文本编辑器中打开了这个文件,所以当我能够useEffect用一个空白保存[],它起作用了。

所以我很困惑。我的猜测是我上面使用的模式不正确,但我不知道“正确”的模式是什么。

任何帮助表示赞赏。

谢谢!

更新:

根据 Robert Cooper 的回答以及 Dan Abramov 的链接文章,我进行了更多实验。我还没有完全到位,但我设法让事情正常进行。

最大的变化是我需要useCallback在我的调度函数周围添加一个,如下所示:

export const useState = () => {
  const dispatch = useDispatch();
  const data = useSelector((state) => state.data);
  const get = React.useCallback((props) => dispatch({type: 'MY_ACTION', payload:props}), [
    dispatch
  ]);

  return {
    data: data,
    get: get,
  };
};

我必须承认,我不完全理解为什么我需要 useCallback 那里,但它的工作原理。

无论如何,然后我的组件看起来像这样:

import * as React from 'react';
import { useState } from './my-state-file';

export default () => {
  const {get, data}  = useState();

  React.useEffect(
    () => {
      get();
      return () => {};
    },
    [get]
  );
  return <div>{do something with data...}</div>;
};

真正的代码有点复杂,我希望useEffect从组件中完全抽象出调用并将其放入useState自定义钩子或从同一my-state-file文件导入的另一个钩子中

1个回答

我相信你遇到的问题是myState你的依赖数组中的值不是相同的值,或者在每次渲染时都有不同的 JavaScript 对象引用。解决这个问题的方法是将 的记忆或缓存版本myState作为依赖项传递给您的useEffect.

您可以尝试使用useMemo自定义返回状态返回的记忆版本useState这可能看起来像这样:

export const useState = () => {
  const dispatch = useDispatch();
  const data = useSelector((state) => state.data);

  return useMemo(() => ({
    data: data,
    get: (props) => dispatch(actionCreators.get(props))
  }), [props]);
};

以下是 Dan Abramov 对useEffect方法中的无限循环的看法

问题:为什么有时会出现无限重取循环?

如果您在没有第二个依赖项参数的情况下进行数据获取,则可能会发生这种情况。没有它,效果会在每次渲染后运行——设置状态将再次触发效果。如果您指定的值始终在依赖项数组中更改,也可能会发生无限循环。您可以通过将它们一一去除来分辨是哪一个。但是,删除您使用的依赖项(或盲目指定 [])通常是错误的解决方法。相反,从源头上解决问题。例如,函数可能会导致此问题,将它们放入效果中、将它们提升出来或使用 useCallback 包装它们会有所帮助。为了避免重新创建对象,useMemo 可以达到类似的目的。

全文在这里:https : //overreacted.io/a-complete-guide-to-useeffect/