使用useEffect React Hook时如何解决缺少依赖项警告?

rus*_*uss 58 reactjs eslint create-react-app react-hooks

使用React 16.8.6(在以前的版本16.8.3中很好),当我尝试防止在获取请求上发生无限循环时,出现此错误

./src/components/BusinessesList.js
Line 51:  React Hook useEffect has a missing dependency: 'fetchBusinesses'.
Either include it or remove the dependency array  react-hooks/exhaustive-deps
Run Code Online (Sandbox Code Playgroud)

我一直找不到停止无限循环的解决方案。我想远离使用useReducer()。我确实在https://github.com/facebook/react/issues/14920找到了这个讨论,在这里可能的解决方案是You can always // eslint-disable-next-line react-hooks/exhaustive-deps if you think you know what you're doing.我不确定自己在做什么,所以我还没有尝试实现它。

我有这个当前设置,React hook useEffect永远/无限循环连续运行,唯一的注释是useCallback()我不熟悉的。

我目前的使用方式useEffect()(类似于,我一开始只想运行一次componentDidMount()

useEffect(() => {
    fetchBusinesses();
  }, []);
Run Code Online (Sandbox Code Playgroud)
const fetchBusinesses = () => {
    return fetch("theURL", {method: "GET"}
    )
      .then(res => normalizeResponseErrors(res))
      .then(res => {
        return res.json();
      })
      .then(rcvdBusinesses => {
        // some stuff
      })
      .catch(err => {
        // some error handling
      });
  };
Run Code Online (Sandbox Code Playgroud)

jpe*_*nna 246

Edit 02/21/2020

Just for completeness:

1. (Stopped working) Use function as useEffect callback

useEffect(fetchBusinesses, [])
Run Code Online (Sandbox Code Playgroud)

2. Declare function inside useEffect()

useEffect(fetchBusinesses, [])
Run Code Online (Sandbox Code Playgroud)

3. Memoize with useCallback()

In this case, if you have dependencies in your function, you will have to include them in the useCallback dependencies array and this will trigger the useEffect again if the function's params change. Besides, it is a lot of boilerplate... So just pass the function directly to useEffect as in 1. useEffect(fetchBusinesses, []).

useEffect(() => {
  function fetchBusinesses() {
    ...
  }
  fetchBusinesses()
}, [])
Run Code Online (Sandbox Code Playgroud)

4. Disable eslint's warning

useEffect(() => {
  fetchBusinesses()
}, []) // eslint-disable-line react-hooks/exhaustive-deps
Run Code Online (Sandbox Code Playgroud)

Original reply

You can set it directly as the useEffect callback:

const fetchBusinesses = useCallback(() => {
  ...
}, [])
useEffect(() => {
  fetchBusinesses()
}, [fetchBusinesses])
Run Code Online (Sandbox Code Playgroud)

It will trigger only once, so make sure all the function's dependencies are correctly set (same as using componentDidMount/componentWillMount...)

  • 我更喜欢禁用警告作为最后的手段,因为未来的开发人员可能会感到困惑或在不知道 linting 已关闭的情况下产生意外的错误 (16认同)
  • 取消 eslint 的警告可以吗? (4认同)
  • `useEffect(fetchBusinesses, [])` 将抛出 _"TypeError: 效果函数不得返回除用于清理的函数之外的任何内容。"_ 因为 `fetchBusinesses` 返回一个承诺。 (3认同)
  • `// eslint-disable-line react-hooks/exhaustive-deps` 很有魅力。 (2认同)
  • 第一个建议并没有令人惊讶地消除警告 (2认同)

Shu*_*tri 68

如果除了效果以外没有在其他地方使用fetchBusinesses方法,则可以将其移至效果中并避免出现警告

useEffect(() => {
    const fetchBusinesses = () => {
       return fetch("theURL", {method: "GET"}
    )
      .then(res => normalizeResponseErrors(res))
      .then(res => {
        return res.json();
      })
      .then(rcvdBusinesses => {
        // some stuff
      })
      .catch(err => {
        // some error handling
      });
  };
  fetchBusinesses();
}, []);
Run Code Online (Sandbox Code Playgroud)

但是,如果在渲染之外使用fetchBusinesses,则必须注意两点

  1. 如果没有通过fetchBusinesses方法传递任何问题,并且在装入过程中使用它的封闭方法,则不会有任何问题。
  2. 您的方法是否依赖于从其封闭的闭包中接收到的某些变量,而您却不是这种情况。
  3. 在每个渲染上,都会重新创建fetchBusinesses,因此将其传递给useEffect会引起问题。因此,如果要将fetchBusinesses传递给依赖项数组,则必须先记住它。

总结一下,我想说的是,如果您在fetchBusinesses外部使用,则可以使用useEffect禁用规则,// eslint-disable-next-line react-hooks/exhaustive-deps否则可以将方法移到useEffect内部

要禁用该规则,您可以这样写

useEffect(() => {
   // other code
   ...

   // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) 
Run Code Online (Sandbox Code Playgroud)

  • 使用 `// eslint-disable-next-line react-hooks/exhaustive-deps` 向 linter 解释您的代码是正确的就像黑客一样。我希望他们会找到另一种解决方案,使 linter 更加智能,能够检测参数何时不是强制性的 (27认同)
  • 我很好地使用了您概述的解决方案。由于设置不同,我在其他地方使用的另一个解决方案是`useCallback()`。因此,例如:```const fetchBusinesses = useCallback(()=> {...},[...]))和`useEffect()`看起来像这样:```useEffect(() => {fetchBusinesses();},[fetchBusinesses]);``` (6认同)
  • @russ,您是对的,如果要将 fetchBusiness 传递给依赖项数组,则需要使用 useCallback 记住它 (5认同)
  • 今天的 linter 仍然很愚蠢,如果你在使用外部变量时想要像 componentDidMount 一样的行为(需要一些但不是全部变量来触发重新渲染,如果它们发生变化),无论你做什么,你都会收到该警告....至少我无法在网上找到解决方案 (5认同)
  • @TapasAdhikary,是的,你可以在 useEffect 中有一个异步函数,你只需要以不同的方式编写它。请检查/sf/ask/3733262501/#53332372 (3认同)

far*_*ard 24

./src/components/BusinessesList.js
Line 51:  React Hook useEffect has a missing dependency: 'fetchBusinesses'.
Either include it or remove the dependency array  react-hooks/exhaustive-deps
Run Code Online (Sandbox Code Playgroud)

这不是JS / React错误,而是eslint(eslint-plugin-react-hooks)警告。

它告诉您钩子依赖于function fetchBusinesses,因此您应该将其作为依赖传递。

useEffect(() => {
  fetchBusinesses();
}, [fetchBusinesses]);
Run Code Online (Sandbox Code Playgroud)

如果在组件中声明了函数,则可能会导致每个渲染调用函数:

./src/components/BusinessesList.js
Line 51:  React Hook useEffect has a missing dependency: 'fetchBusinesses'.
Either include it or remove the dependency array  react-hooks/exhaustive-deps
Run Code Online (Sandbox Code Playgroud)

因为每次使用新引用重新声明函数

正确的方法是:

const Component = () => {
  /*...*/

  // keep function reference
  const fetchBusinesses = useCallback(() => {
    fetch('/api/businesses/')
      .then(...)
  }, [/* additional dependencies */]) 

  useEffect(() => {
    fetchBusinesses();
  }, [fetchBusinesses]);

  /*...*/
}
Run Code Online (Sandbox Code Playgroud)

或只是在定义功能 useEffect

更多:https//github.com/facebook/react/issues/14920

  • 这会导致出现新的错误'''第20行:'fetchBusinesses'函数使useEffect Hook的依赖关系(在第51行)在每个渲染器上都改变。将其移至useEffect回调中。或者,将'fetchBusinesses'定义包装到其自己的useCallback()Hook中。'' (8认同)
  • 我同意这是消除 linter 警告的解决方案。但我不明白为什么 linter 会发出警告。因为无论如何,你所做的只是治愈一种不存在的疾病的症状。React 使用依赖项数组来确定何时执行传递给 useEffect 的函数,因此在这种情况下传递 fetchBusinesses 在我看来是不必要的。如果我错了,请纠正我。 (4认同)
  • 解决方案很好,如果在函数上修改另一个状态,则必须添加依赖项以避免另一个意外行为 (2认同)
  • 同意 - linter 警告对于 `useCallback` 和 `useMemo` 非常有用,但是假设您想要运行 `useEffect` 的业务逻辑与每个依赖项更新时的业务逻辑相同是一个无效的假设。例如,有很多原因您可能只想在组件安装上运行效果,而 `useEffect(() => { ... }, [])` 是实现这一目标的完美工具。React/es-lint 正在做一些不需要太难的东西。 (2认同)

Ste*_*e L 17

React 也给出了解决方案。他们建议您使用useCallback它将返回您的函数的记忆版本:

'fetchBusinesses' 函数使 useEffect Hook(在第 NN 行)的依赖关系在每次渲染时发生变化。要解决此问题,请将 'fetchBusinesses' 定义包装到它自己的 useCallback() Hook react-hooks/exhaustive-deps 中

useCallback使用简单,因为它具有与useEffect. 不同之处在于 useCallback 返回一个函数。它看起来像这样:

 const fetchBusinesses = useCallback( () => {
        return fetch("theURL", {method: "GET"}
    )
    .then(() => { /* Some stuff */ })
    .catch(() => { /* Some error handling */ })
  }, [/* deps */])
  // We have a first effect that uses fetchBusinesses
  useEffect(() => {
    // Do things and then fetchBusinesses
    fetchBusinesses();
  }, [fetchBusinesses]);
   // We can have many effects that use fetchBusinesses
  useEffect(() => {
    // Do other things and then fetchBusinesses
    fetchBusinesses();
  }, [fetchBusinesses]);
Run Code Online (Sandbox Code Playgroud)


小智 15

只需在下一行禁用 ESLint 即可;

useEffect(() => {
   fetchBusinesses();
// eslint-disable-next-line
}, []);
Run Code Online (Sandbox Code Playgroud)

通过这种方式,您可以像安装组件一样使用它(调用一次)。

更新

或者

const fetchBusinesses = useCallback(() => {
 // Your logic in here
 }, [someDeps])

useEffect(() => {
   fetchBusinesses();
// No need to skip the ESLint warning
}, [fetchBusinesses]);
Run Code Online (Sandbox Code Playgroud)

每次someDeps发生变化时,都会调用 fetchBusinesses 。

  • @RotimiBest - 这样做会导致无限重新渲染,如问题中所述 (14认同)
  • @user210757等等,但是为什么会导致无限循环,它不像你在从服务器获取数据后设置状态。如果您要更新状态,只需在调用“useEffect”中的函数检查状态是否为空之前编写一个 if 条件即可。 (2认同)

Jor*_*els 11

这些警告对于查找不一致更新的组件非常有帮助:从依赖项列表中省略函数是否安全?.

但是,如果您想删除整个项目中的警告,您可以将其添加到您的 ESLint 配置中:

  {
  "plugins": ["react-hooks"],
  "rules": {
    "react-hooks/exhaustive-deps": 0
    }
  }
Run Code Online (Sandbox Code Playgroud)


hel*_*joe 9

本文是关于使用钩子获取数据的一个很好的入门:https://www.robinwieruch.de/react-hooks-fetch-data/

本质上,将 fetch 函数定义包含在其中useEffect

useEffect(() => {
  const fetchBusinesses = () => {
    return fetch("theUrl"...
      // ...your fetch implementation
    );
  }

  fetchBusinesses();
}, []);
Run Code Online (Sandbox Code Playgroud)


Beh*_*imi 8

实际上,当您使用钩子进行开发时,这些警告非常有用。但在某些情况下,它可能会让你感到刺痛。特别是当您不需要监听依赖项更改时。

如果您不想放入fetchBusinesses钩子的依赖项,您可以简单地将其作为参数传递给钩子的回调,并将 main 设置fetchBusinesses为其默认值,如下所示:

useEffect((fetchBusinesses = fetchBusinesses) => {
   fetchBusinesses();
}, []);
Run Code Online (Sandbox Code Playgroud)

这不是最佳实践,但在某些情况下可能很有用。

另外,正如Shubham 所写,您可以添加以下代码来告诉 ESLint 忽略对钩子的检查。

// eslint-disable-next-line react-hooks/exhaustive-deps
Run Code Online (Sandbox Code Playgroud)


Kas*_*hif 8

你试试这个方法:

const fetchBusinesses = () => {
    return fetch("theURL", {method: "GET"})
        .then(res => normalizeResponseErrors(res))
        .then(res => {
            return res.json();
        })
        .then(rcvdBusinesses => {
            // Some stuff
        })
        .catch(err => {
            // Some error handling
        });
  };
Run Code Online (Sandbox Code Playgroud)

useEffect(() => {
    fetchBusinesses();
});
Run Code Online (Sandbox Code Playgroud)

它适合你。

但我的建议是尝试这种方式,它也适合你。这比以前的方法要好。我这样使用它:

useEffect(() => {
    const fetchBusinesses = () => {
        return fetch("theURL", {method: "GET"})
            .then(res => normalizeResponseErrors(res))
            .then(res => {
                return res.json();
            })
            .then(rcvdBusinesses => {
                // Some stuff
            })
            .catch(err => {
                // Some error handling
            });
    };

    fetchBusinesses();
}, []);
Run Code Online (Sandbox Code Playgroud)

如果您根据特定 id 获取数据,则添加回调 useEffect [id]。那么它就无法向您显示警告 React Hook useEffect has a missing dependency: 'any thing'. Either include it or remove the dependency array


Yas*_*sin 6

const [mount, setMount] = useState(false)
const fetchBusinesses = () => {
   // Function definition
}
useEffect(() => {
   if(!mount) {
      setMount(true);
      fetchBusinesses();
   }
},[fetchBusinesses]);
Run Code Online (Sandbox Code Playgroud)

这是一个非常简单的解决方案,您不需要覆盖 ESLint 警告。只需维护一个标志来检查组件是否已安装。


Era*_* Or 6

这不是特定于问题用例的答案,而是更一般的情况,并涵盖 useEffect 或 extract 和 import 未发出警告时的情况。useRef 场景:

有时情况是 useEffect 应该有空数组,并且您仍然想在状态的 useEffect 部分中使用,但您仍然不想将它们作为依赖项注入,您也可能尝试了 useCallback ,现在反应抱怨依赖项useCallback 和你卡住了。在某些情况下你可以使用useRef。例如:

const locationRef = useRef(location);
useEffect(()=>{
const qs = locationRef.current.search
...
},[])
Run Code Online (Sandbox Code Playgroud)

使用此技术时应小心,并注意 useRef 不会激活渲染过程。


sya*_*rul 5

好吧,如果你想以不同的方式研究这个问题,你只需要知道 React 有哪些选项是非的exhaustive-deps。您不应该在效果中使用闭包函数的原因之一是在每次渲染时,它将再次重新创建/销毁。

因此,钩子中有多个 React 方法被认为是稳定且非耗尽的,您不必应用依赖useEffect项,从而不会破坏react-hooks/exhaustive-deps. 例如,useReducer或 的第二个返回变量useState是一个函数。

const [,dispatch] = useReducer(reducer, {});

useEffect(() => {
    dispatch(); // Non-exhausted - ESLint won't nag about this
}, []);
Run Code Online (Sandbox Code Playgroud)

因此,您可以让所有外部依赖项与您的减速器函数中的当前依赖项共存。

const [,dispatch] = useReducer((current, update) => {
    const { foobar } = update;
    // Logic

    return { ...current, ...update };
}, {});

const [foobar, setFoobar] = useState(false);

useEffect(() => {
    dispatch({ foobar }); // non-exhausted `dispatch` function
}, [foobar]);
Run Code Online (Sandbox Code Playgroud)