【问题标题】:How to do fetch with React Hooks; ESLint enforcing `exhaustive-deps` rule, which causes infinite loop如何使用 React Hooks 进行获取; ESLint 强制执行“exhaustive-deps”规则,导致无限循环
【发布时间】:2019-10-31 12:13:47
【问题描述】:

总的来说,我对 React 钩子很陌生,对 react-redux 中的 useSelectoruseDispatch 非常陌生,但是当我的组件加载时,我无法执行一个简单的 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-sagaaxios 发生的网络请求,并且已经在生产代码中运行了一段时间。到目前为止,一切都很好。现在我想在功能组件中使用它,所以我写了这个:

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 自定义钩子中,或者从同一个 @987654342 导入的另一个钩子中@文件。

【问题讨论】:

    标签: reactjs react-redux eslint react-hooks


    【解决方案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/

    【讨论】:

    • 感谢您的回复。还在咀嚼它。我想也许最简单的解决方法是解构myState,这样我的效果代码就会是React.useEffect( () =&gt; { get(); return () =&gt; {}; }, [get] );(类似于文章中Abramov 的[dispatch] 示例),但这并没有改变行为,所以我显然仍然失踪某物。 useMemo 没有按照您的建议工作,因为在执行 getclear 之前,props 不可用。所以我仍然要么缺少一些简单而愚蠢的东西,要么我对这一切如何运作的心理模型还不正确。我会坚持下去的!
    • 我用一种可能的解决方案更新了我的问题。我对它还不是 100% 满意,但至少我有一些有用的东西。再次感谢您的帮助!
    • 啊,好吧,很高兴你能猜到。
    • 我还不能 100% 确定钩子实际上是比 HOC 更好的模式。每个人都在说他们是,所以我想我只需要相信他们最终会点击,我的编码生活会很美好。我知道它应该是直接的 Javascript,理论上它可以让事情变得更好,但是像 useEffectuseCallback 这样的生命周期的东西让你在查看代码时很难理解钩子。
    • 我倾向于同意。与通过类提供的生命周期方法相比,useEffectuseCallback 所做的事情并不直观。
    猜你喜欢
    • 2020-11-20
    • 1970-01-01
    • 2020-06-09
    • 1970-01-01
    • 2021-06-06
    • 2022-08-18
    • 2021-04-18
    • 2020-03-11
    相关资源
    最近更新 更多