如何在 React 中使用 hooks 实现 componentDidMount 以符合 EsLint 规则“react-hooks/exhaustive-deps”:“warn”?
Posted
技术标签:
【中文标题】如何在 React 中使用 hooks 实现 componentDidMount 以符合 EsLint 规则“react-hooks/exhaustive-deps”:“warn”?【英文标题】:How to implement componentDidMount with hooks in React to be in line with the EsLint rule "react-hooks/exhaustive-deps": "warn"? 【发布时间】:2019-11-28 08:44:16 【问题描述】:根据 React 官方文档,componentDidMount
在 hooks 中被翻译为:
useEffect(() =>
//code here
,[])
所以假设我想在这个钩子中做一个 api 调用:
useEffect(() =>
getActiveUser();
,[])
添加eslint规则"react-hooks/exhaustive-deps"
后,这是一个lint错误。为了使其静音,我可以将 getActiveUser
函数放入数组中,一切正常。
但这是否违反了文档?我的印象是数组检查道具的变化。我还想指出,API 调用是在没有 prop/id 的情况下进行的,所以我可以理解必须这样做的事实:
useEffect(() =>
getActiveUser(someId);
,[getActiveUser, someId])
那么这里发生了什么?添加Eslint规则意味着效果里面的数组不能再为空了?
【问题讨论】:
“一个常见的错误是认为函数不应该是依赖项。....但是简单地省略本地函数的问题是很难判断我们是否将所有情况都处理为组件增长!” via 所以你不能确定(或者你能不能)这个函数会改变你在哪里定义它。因此,为了安全起见,要么将其添加到依赖项中,要么在useEffect
中定义它。
***.com/questions/55840294/…的可能重复
将你的函数放入 useEffect 和 Id 到数组中
@Shubham Khatri 不是重复的,我在这里解决一个不同的问题,即:使用 eslint 规则,这是否意味着数组不能再为空?
也许更好地重新考虑它的工作原理并提供 getActiveUser
作为依赖项。见讨论github.com/facebook/react/issues/14920#issuecomment-470913287
【参考方案1】:
在哪里声明 getActiveUser
很重要。问题没有具体说明,但我假设您的组件看起来像这样:
const MyComponent = (props) =>
const getActiveUser() =>
//...
useEffect(() =>
getActiveUser();
, []) // Lint error.
return <></>;
如果您的组件看起来像这样,您将不会收到 linter 错误:
const getActiveUser() =>
//...
const MyComponent = (props) =>
useEffect(() =>
getActiveUser();
, []) // No error
return <></>;
那么为什么第一个是 linter 错误而第二个不是呢? linter 规则的重点是避免由于过时的 props 或 state 引起的问题。虽然getActiveUser
本身不是道具或状态,但当它在组件内部定义时,它可能依赖于可能是陈旧的道具或状态。
考虑这段代码:
const MyComponent = (userId) =>
const [userData, setUserData] = useState(null);
const getActiveUser() =>
setUserData(getData(userId)); // More realistically this would be async
useEffect(() =>
getActiveUser();
, []);
//...
尽管 useEffect
依赖于 userId
属性,但它只运行一次,因此如果 userId
更改,userId
和 userData
将不同步。也许这是您的意图,但就 linter 规则而言,它看起来像一个错误。
在getActiveUser
定义在组件外部的情况下,它不可能(或至少不合理地)依赖于组件的状态或道具,因此 linter 规则没有问题。
那么如何解决这个问题?好吧,如果getActiveUser
不需要在组件内部定义,只需将其移出组件即可。
或者,如果您确定只希望在组件挂载时运行此行为,并且不会因道具更改而导致问题(最好假设所有道具都可以更改),那么您可以禁用linter 规则。
但假设这些都不是这种情况......
无解(效果太多)
正如您所指出的,将 getActiveUser
添加到 linter 数组可以解决问题:
const MyComponent = (userId) =>
const getActiveUser() =>
//...
useEffect(() =>
getActiveUser();
, [getActiveUser]) // No error... but probably not right.
return <></>;
但是getActiveUser
每次渲染都是不同的函数实例,所以就useEffect
而言,deps数组每次渲染都会改变,这会导致每次渲染后调用API,这几乎肯定不是你想要的.
一个脆弱的解决方案
由于我示例中的根本问题是 userId
属性可能会更改,因此您还可以通过将 userId
添加到 useEffect
依赖项来解决此问题:
const MyComponent = (userId) =>
const getActiveUser() =>
// Uses userId
useEffect(() =>
getActiveUser();
// Linter is still unhappy, so:
// eslint-disable-next-line react-hooks/exhaustive-deps
, [userId])
return <></>;
这行为正确 - 没有额外的 API 调用或陈旧的数据 - 但 linter 仍然不开心:知道我们已经通过依赖 @987654343 的所有东西修复了对 getActiveUser
的依赖是不够聪明的@ 取决于。
这很脆弱:如果您在将来添加 getActiveUser
所依赖的 prop 或 state,而忘记在此处添加它,您将遇到过时数据问题。
更好的解决方案
所以推荐的解决方案是:
const MyComponent = (userId) =>
const getActiveUsers = useCallback(() =>
// uses userId
, [userId]);
useEffect(() =>
getActiveUser();
, [getActiveUsers]) // No error
return <></>;
通过将getActiveUsers
包装在useCallback
中,函数实例仅在需要时被替换:当userId
发生变化时。这意味着useEffect
也仅在需要时运行:当getActiveUsers
更改时(即userId
更改时)。
linter 对这个解决方案很满意,如果您向 getActiveUser
引入新的依赖项,您只需更改其 useCallback
deps,而不是 useEffect
。
Dan Abramov 的博文 A Complete Guide to useEffect
对此进行了更详细的介绍。
【讨论】:
以上是关于如何在 React 中使用 hooks 实现 componentDidMount 以符合 EsLint 规则“react-hooks/exhaustive-deps”:“warn”?的主要内容,如果未能解决你的问题,请参考以下文章
React-Apollo-Hooks 使用Mutation 传递空变量?