使用 useEffect React Hook 时如何修复缺少依赖项警告

How to fix missing dependency warning when using useEffect React Hook

使用 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

我一直找不到停止无限循环的解决方案。我想远离使用 useReducer()。我确实找到了这个讨论 [ESLint] Feedback for 'exhaustive-deps' lint rule #14920,其中一个可能的解决方案是 You can always // eslint-disable-next-line react-hooks/exhaustive-deps if you think you know what you're doing. 我对自己正在做的事情没有信心,所以我没有尝试实施它刚刚。

我有这个当前设置,,唯一的评论是关于 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
      });
  };

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

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

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

  fetchBusinesses();
}, []);
./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

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

它告诉你钩子依赖于函数 fetchBusinesses,所以你应该将它作为依赖项传递。

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

如果在如下组件中声明函数,则可能导致在每次渲染时调用该函数:

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

  // New function declaration every render
  const fetchBusinesses = () => {
    fetch('/api/businesses/')
      .then(...)
  }

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

  /*...*/
}

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

正确的做法是:

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

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

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

  /*...*/
}

或者直接在useEffect中定义函数。

更多:[ESLint] Feedback for 'exhaustive-deps' lint rule #14920

如果您没有在效果之外的任何地方使用 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();
}, []);

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

  1. 你有什么问题没有传递fetchBusinesses作为方法,当它在挂载期间使用它的封闭闭包时?
  2. 您的方法是否依赖于它从封闭闭包中接收到的某些变量?你不是这样的。
  3. 在每次渲染时,将重新创建 fetchBusinesses,因此将其传递给 useEffect 会导致问题。因此,如果要将 fetchBusinesses 传递给依赖项数组,首先必须记住它。

总而言之,如果您在 useEffect 之外使用 fetchBusinesses,您可以使用 // eslint-disable-next-line react-hooks/exhaustive-deps 禁用规则,否则您可以将方法移至 useEffect

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

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

   // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) 

只需为下一行禁用 ESLint;

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

这样,您就可以像挂载(调用一次)组件一样使用它。

已更新

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

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

每次 someDeps 更改时都会调用 fetchBusinesses。

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

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]);

  // ...
}

React也给出了解决方案。他们建议您使用 useCallback,这将 return 一个 memoize 版本的函数:

The 'fetchBusinesses' function makes the dependencies of the useEffect Hook (at line NN) change on every render. To fix this, wrap the 'fetchBusinesses' definition into its own useCallback() Hook react-hooks/exhaustive-deps

useCallback 使用简单,因为它与 useEffect 具有相同的签名。区别在于 useCallback return 是一个函数。 它看起来像这样:

 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]);

如果您正在创建新应用或有足够的灵活性,状态管理库有很好的选择。查看后坐力。

为了完整起见:

1. (停止工作) 使用函数作为 useEffect 回调

useEffect(fetchBusinesses, [])

2。在 useEffect()

中声明函数
useEffect(() => {
  function fetchBusinesses() {
    ...
  }
  fetchBusinesses()
}, [])

3。用 useCallback()

记忆

在这种情况下,如果您的函数中有依赖项,则必须将它们包含在 useCallback 依赖项数组中,如果函数的参数发生变化,这将再次触发 useEffect。此外,它有很多样板......所以只需将函数直接传递给 useEffect 就像 1. useEffect(fetchBusinesses, []).

const fetchBusinesses = useCallback(() => {
  ...
}, [])
useEffect(() => {
  fetchBusinesses()
}, [fetchBusinesses])

4。函数的默认参数

根据

的建议

It's not best practice, but it could be useful in some cases.

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

5。创建自定义挂钩

创建一个自定义挂钩并在您只需要 运行 函数时调用它。它可能更干净。您还可以 return 回调以在需要时重新设置 运行 “初始化”。

// customHooks.js
const useInit = (callback, ...args) => {
  const [mounted, setMounted] = useState(false)
  
  const resetInit = () => setMounted(false)

  useEffect(() => {
     if(!mounted) {
        setMounted(true);
        callback(...args);
     }
  },[mounted, callback]);

  return [resetInit]
}

// Component.js
return ({ fetchBusiness, arg1, arg2, requiresRefetch }) => {
  const [resetInit] = useInit(fetchBusiness, arg1, arg2)

  useEffect(() => {
    resetInit()
  }, [requiresRefetch, resetInit]);

6。禁用 eslint 的警告

禁用警告应该是你最后的手段,但当你这样做时,最好内联并显式,因为未来的开发人员可能会感到困惑或在不知道 linting 已关闭的情况下产生意外错误

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

这些警告对于查找更新不一致的组件非常有帮助:Is it safe to omit functions from the list of dependencies?.

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

  {
  "plugins": ["react-hooks"],
  "rules": {
    "react-hooks/exhaustive-deps": 0
    }
  }

其实warnings在你用hooks开发的时候是很有用的。但在某些情况下,它可能会刺激你。特别是当您不需要监听依赖项更改时。

如果你不想把 fetchBusinesses 放在钩子的依赖项中,你可以简单地将它作为参数传递给钩子的回调并将主 fetchBusinesses 设置为它的默认值像这样:

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

这不是最佳做法,但在某些情况下可能会有用。

此外,作为,您可以添加以下代码来告诉 ESLint 忽略对您的钩子的检查。

// eslint-disable-next-line react-hooks/exhaustive-deps
const [mount, setMount] = useState(false)
const fetchBusinesses = () => {
   // Function definition
}
useEffect(() => {
   if(!mount) {
      setMount(true);
      fetchBusinesses();
   }
},[fetchBusinesses, mount]);

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

你这样试试:

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
        });
  };

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

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

只需将函数作为参数传递到 useEffect 数组中...

useEffect(() => {
   functionName()
}, [functionName])

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

因此在钩子中有多个 React 方法被认为是稳定的和非耗尽的,你不必应用到 useEffect 依赖项,反过来也不会破坏 [=14] 的规则参与=].例如,useReduceruseState 的第二个 return 变量是一个函数。

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

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

因此,反过来,您可以让所有外部依赖项与您的 reducer 函数中的当前依赖项共存。

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]);

如果您在 useEffect 中使用的变量在组件内部定义或作为 prop 传递给组件,则会出现此警告。由于您在同一个组件中定义了 fetchBusinesses(),因此您必须将其传递给依赖项数组。

但是如果您导入了 fetchBusinesses() 然后在 useEffect 中使用它,您就不需要将它添加到依赖项数组中。这就是我们实际设置 Redux 应用程序的方式:我们总是将我们的动作创建者和 运行 导入到 useEffect 中,而不将其添加到依赖项数组。

useMemo也是如此。

在我的例子中,我的局部变量 organization 收到此警告,当我将 organization 放入依赖项数组时,useEffect 将无限获取。因此,如果您遇到像我这样的问题,请将 useEffect 与依赖数组一起使用并拆分:

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

发件人:

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

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

收件人:

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

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

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

您可以通过传递对它的引用来摆脱这个 Es-lint 警告:

下面提到的示例,但是您可以在 link 上观看解决方案: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 步:将业务逻辑移至单独的 const。

现在警告是: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]);

似乎是组件中声明的fetchBusinesses函数。 这意味着在每个渲染中它都声明了触发钩子的新函数。

有 2 种方法可以解决此问题。

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

  2. useCallback 钩子包装 fetchBusinesses 函数。

首选第一个选项。

这不是特定于问题用例的答案,而是更一般的情况,涵盖了 useEffect 或 extract and import 不起作用的情况。 useRef 场景:

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

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

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

使用 UseEffect fetchBusinesses 调用函数在 useEffect() 中声明通过在调用函数名称之后声明一个 const 变量,

useEffect(()=>{
const fetchBusinesses=()=>{
   console.log(useeffect fetchbussinesses functions)
}
 fetchBusinesses();
},[declare the variable used in useeffect hooks ])

您正在使用 useEffect,当您这样做时,您经常需要使用一些变量,这些变量在您的组件中用作 props 或状态。

eslint 中内置了一条规则,希望您在 useEffect 依赖项数组中引用任何不同的 prop 或状态片段。这是控制何时执行 useEffect 的数组。该规则希望看到它列在该数组中,该数组决定何时 re-run useEffect 函数。

因此您需要添加 [fetchBusinesses] 并且警告应该消失。

现在,为什么这条规则要我们把它放在那里?

在某些情况下,使用 useEffect 而未正确列出数组中的所有状态和道具可能会导致奇怪且难以调试的问题。

所以这条规则是为了帮助避免那些可能会出现 useEffect.

的难以理解的问题

现在,任意添加到该数组也会导致错误。因此,无论哪种方式,您都会 运行 遇到必须解决的错误。根据您的评论,这似乎为您解决了问题,但我想进一步调查,看看您是否有机会在 Network 选项卡中收到第二个 GET 请求在 Chrome 中将 fetchBusinesses 函数添加到 useEffect 数组后。