如何使用React Hooks进行获取; ESLint强制执行“穷举下降”规则,这会导致无限循环

时间:2019-06-17 21:29:41

标签: reactjs react-redux eslint react-hooks

我一般来说对React钩子还是很陌生的,对useSelector中的useDispatchreact-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-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自定义钩子或从中导入的另一个钩子相同的my-state-file文件。

1 个答案:

答案 0 :(得分: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/