如何修复使用useEffect React Hook时出现的缺失依赖警告

819

使用React 16.8.6(之前的版本16.8.3没有问题),在我试图防止一个fetch请求进入无限循环时,我会遇到这个错误:

./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

我一直找不到停止无限循环的解决方案。我希望避免使用useReducer()。我找到了这个讨论[ESLint] Feedback for 'exhaustive-deps' lint rule #14920,其中一个可能的解决方案是如果你认为自己知道在做什么,你可以总是 // eslint-disable-next-line react-hooks/exhaustive-deps。我对自己正在做的事情没有信心,所以我还没有尝试实现它。

我目前的设置是这样的:React hook useEffect runs continuously forever/infinite loop,唯一的评论是关于useCallback(),我不熟悉它。

我目前如何使用useEffect()(我只想在开始时运行一次,类似于componentDidMount()):

useEffect(() => {
    fetchBusinesses();
  }, []);
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
      });
  };

25
由于这个问题的流量很大,这里提供 Dan Abramov 的博客链接,在该博客中他详细解释了 useEffect 及其依赖项。 - chetan
7
React有一个功能请求,希望改进useEffect API以避免此问题,明确将effect触发器与effect依赖项分开:https://github.com/facebook/react/issues/22132。自从Next.js启用了默认的linting功能后,这个警告每天在世界各地出现了数百万次,这种情况必须得到解决。 - Eric Burel
8
同意,官方文档中的内容完全不清晰。像 React 这样的库不应该需要通过论坛和博客文章才能让它正常工作。 - Kokodoko
23个回答

4

这不是针对问题场景的特定答案,而是更普遍的情况,并且涵盖当useEffect或extract和import不起作用时的情况。 useRef 的应用场景:

有时候,useEffect应该有一个空数组,但你仍然希望在useEffect内部使用一些状态,但你不想将它们作为依赖项注入,同时你可能已经尝试过 useCallback ,现在 React 抱怨 useCallback 的依赖关系,你陷入了困境。 在这种情况下,你可以在某些情况下使用 useRef。例如:

const locationRef = useRef(location);
useEffect(()=>{
const qs = locationRef.current.search
...
},[])

使用该技术时,您应当小心并了解 useRef 不会触发渲染过程。


4

如果你想以不同的方式查看这个问题,你只需要了解React具有哪些非“exhaustive-deps”的选项。在effect中使用闭包函数的一个原因是每次渲染它都会被重新创建/销毁。

所以,在React Hooks中有多种被认为是稳定和未耗尽的方法,你不必应用于“useEffect”的依赖项,并且不会违反“react-hooks/exhaustive-deps”的规则。例如,“useReducer”或“useState”的第二个返回变量就是一个函数。

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

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

因此,您可以将所有外部依赖项与您当前的依赖项共存于您的减速器函数中。
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]);

3

在我的情况下,我的本地变量organization出现了这个警告,当我将organization放入依赖数组中时,useEffect会无限制地进行获取。因此,如果您遇到类似我的问题,请使用带有依赖关系数组的useEffect并进行分离:

因为如果您有多个调用API的函数来修改状态,它会多次调用useEffect

来自:

  const { organization } = useSelector(withOrganization)
  const dispatch = useDispatch()

  useEffect(() => {
    dispatch(getOrganization({}))
    dispatch(getSettings({}))
    dispatch(getMembers({}))
  }, [dispatch, organization])

To:

  const { organization } = useSelector(withOrganization)
  const dispatch = useDispatch()

  useEffect(() => {
    dispatch(getOrganization({}))
    dispatch(getSettings({}))
  }, [dispatch, organization])

  useEffect(() => {
    dispatch(getMembers({}))
  }, [dispatch])

2

您可以删除第二个参数类型数组[],但是每次更新时也将调用fetchBusinesses()。如果您愿意,可以在fetchBusinesses()实现中添加一个IF语句。

React.useEffect(() => {
  fetchBusinesses();
});

另一种方法是在组件之外实现fetchBusinesses()函数。只需不要忘记将任何依赖参数传递到fetchBusinesses(dependency)调用中,如果有的话。
function fetchBusinesses (fetch) {
  return fetch("theURL", { method: "GET" })
    .then(res => normalizeResponseErrors(res))
    .then(res => res.json())
    .then(rcvdBusinesses => {
      // some stuff
    })
    .catch(err => {
      // some error handling
    });
}

function YourComponent (props) {
  const { fetch } = props;

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

  // ...
}

移除依赖数组括号导致了一个问题,在这个组件中,我有一个表单,会导致无限重新渲染。 - Harvester Haidar

2
如果您想禁用这个无用的消息,只需在文件开头添加以下内容。
/* eslint-disable react-hooks/exhaustive-deps */

这将禁用整个文件的警告。


“// eslint-disable-next-line” 对我无效,但这个完成了任务。 - amerw
只是想知道,这条消息真的毫无用处吗?还是有时候会有用呢? - Miguel Jara

1
您可以通过传递一个引用来消除这个 Es-lint 警告:
以下是示例,但您可以在此链接上观看解决方案:https://www.youtube.com/watch?v=r4A46oBIwZk&t=8s 警告: 第13行第8列:React Hook React.useEffect 缺少依赖项:'history' 和 'currentUser?.role'。要么包含它们,要么删除依赖项数组 react-hooks/exhaustive-deps
React.useEffect(() => {
    if (currentUser?.role !== "Student") {
        return history.push("/")
    }
}, [])

分辨率: 步骤1:将业务逻辑移动到单独的常量中。

现在的警告是:React Hook React.useEffect缺少依赖项:'roleChecking'。

const roleChecking = () =>{
   if (currentUser?.role !== "Student") {
        return history.push("/")
    }
}

React.useEffect(() => {
    roleChecking()
}, [])

最后一步是创建对该函数的引用:
  const roleRef = React.useRef();

  const roleChecking = () => {
    if (currentUser?.role !== "Student") {
      return history.push("/");
    }
  };
  roleRef.current = roleChecking;

  React.useEffect(() => {
   return roleRef.current();
  }, [currentUser?.role]);

1

搜索关键字以了解每个警告的详细信息。 要忽略警告,请在该行之前添加 // eslint-disable-next-line。

例如:useEffect 中使用的函数引起了警告。

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

要忽略警告,我们只需在警告行之前添加“// eslint-disable-next-line”,即:

useEffect(() => {
  handleConnectWallet();
  // eslint-disable-next-line
}, []);


1

在组件中声明了fetchBusinesses函数。这意味着每次渲染都会声明新函数,从而触发钩子。

有两种方法可以解决这个问题。

  1. fetchBusinesses函数声明移出组件。

  2. 使用useCallback钩子包装fetchBusinesses函数。

第一种选项更可取。


1
这个警告会出现在你在useEffect中使用的变量被定义在组件内部或作为prop传递给组件时。由于你在同一组件中定义了fetchBusinesses(),而且eslint遵循该规则,因此你必须将其传递给依赖数组。但在你的情况下,只传递[]也可以。

在这种情况下,它可以工作,但如果fetchBusinesses在函数内部使用setState并调用它将重新渲染组件,因此您的fetchBusinesses将更改,因此useEffect将运行,这将创建一个无限循环。因此,盲目遵循eslint可能会导致额外的错误。

解决方案是在useCallback中使用[],以使eslint满意。

const memoizedFetchBusinesses=useCallback(
        fetchBusinesses,
        [] // unlike useEffect you always have to pass a dependency array
       )

当您的组件首次呈现时,内存中创建了一个名为fetchBusinessess的函数,并创建了memoizedFetchBusinesses变量,该变量也引用了同一内存中的函数。
在第一次重新渲染后,将再次创建一个名为fetchBusinessess的函数,但这次在不同的内存位置。由于useCallback中有[ ],memoizedFetchBusinesses将提供相同内存中原始的fetchBusinesses函数。在此处使用useCallback将为您提供在组件首次渲染时创建的同一函数引用。
useEffect(()=>{
    memoizedFetchBusinesses();
},[memoizedFetchBusinesses]) 

相反,您可以像这样定义函数:

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

然后在 useEffect 中

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

2
即使从您的存储库导入函数,您仍会收到警告。因为您的函数将通过mapDispatchToProps调用或使用connect标记的第二个参数传递给props对象。connect(mapStateToProps, {fetchBusinesses})(Component) - osmancakirio
@osmancakirio,你在这个情况下找到解决方法了吗?我也有同样的问题... - ndtreviv
1
@ndtreviv,我重构了组件,现在使用redux-hooks而不是connect标签。然后我将dispatch函数提供给依赖项数组。这也是redux开发人员推荐的做法,因为他们说这样做是安全的,因为对dispatch函数的引用几乎永远不会改变。 - osmancakirio

0
使用 UseEffect,通过在 useEffect() 中声明 const 变量并调用函数名来调用 fetchBusinesses 函数。
useEffect(()=>{
const fetchBusinesses=()=>{
   console.log(useeffect fetchbussinesses functions)
}
 fetchBusinesses();
},[declare the variable used in useeffect hooks ])

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接