javascript - 如何在 React 中使用 hooks 实现 componentDidMount 以符合 EsLint 规则“react-hooks/exhaustive-deps”:“warn”?
问题描述
根据官方 React 文档,componentDidMount
在钩子中翻译为:
useEffect(() => {
//code here
},[])
所以假设我想在这个钩子中做一个 api 调用:
useEffect(() => {
getActiveUser();
},[])
添加 eslint 规则后"react-hooks/exhaustive-deps"
,这是一个 lint 错误。为了使其静音,我可以将getActiveUser
函数放入数组中,一切正常。
但这是否违反文档?我的印象是数组检查道具的变化。我还想指出,API 调用是在没有 prop/id 的情况下进行的,所以我可以理解必须这样做的事实:
useEffect(() => {
getActiveUser(someId);
},[getActiveUser, someId])
那么这里发生了什么?添加Eslint规则意味着效果里面的数组不能再为空了?
解决方案
在哪里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
它本身不是 props 或 state,但当它在组件内部定义时,它可能依赖于 props 或 state,这可能是陈旧的。
考虑这段代码:
const MyComponent = ({userId}) => {
const [userData, setUserData] = useState(null);
const getActiveUser() => {
setUserData(getData(userId)); // More realistically this would be async
}
useEffect(() => {
getActiveUser();
}, []);
//...
}
尽管这useEffect
取决于userId
道具,但它只运行一次,因此如果发生更改,则userId
和userData
将不同步。userId
也许这是您的意图,但出于 linter 规则的目的,它看起来像一个错误。
在组件外部定义的情况下getActiveUser
,它不可能(或至少不合理地)依赖于组件的状态或道具,因此 linter 规则没有问题。
那么如何解决这个问题呢?好吧,如果getActiveUser
不需要在组件内部定义,只需将其移出组件即可。
或者,如果您确定只希望在安装组件时运行此行为,并且不会由于 props 更改而导致问题(最好假设所有 props 都可以更改),那么您可以禁用 linter 规则。
但假设这两种情况都不是……
无解(效果太多)
正如您所指出的,添加getActiveUser
到 linter 数组会使问题消失:
const MyComponent = ({userId}) => {
const getActiveUser() => {
//...
}
useEffect(() => {
getActiveUser();
}, [getActiveUser]) // No error... but probably not right.
return <></>;
}
但是getActiveUser
每次渲染都是不同的函数实例,所以就每个渲染useEffect
而言,deps 数组都会改变,这会导致每次渲染后调用 API,这几乎肯定不是你想要的。
脆弱的解决方案
由于我的示例中的根本问题是userId
prop 可能会更改,因此您还可以通过添加依赖项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 仍然不开心:知道我们已经getActiveUser
通过依赖所有依赖的东西来修复依赖是不够聪明的getActiveUser
。
这很脆弱:如果您在未来getActiveUser
添加依赖于的道具或状态,而忘记在此处添加,您将遇到过时数据问题。
更好的解决方案
所以推荐的解决方案是:
const MyComponent = ({userId}) => {
const getActiveUsers = useCallback(() => {
// uses userId
}, [userId]);
useEffect(() => {
getActiveUser();
}, [getActiveUsers]) // No error
return <></>;
}
通过包装getActiveUsers
,useCallback
函数实例仅在需要时被替换:当userId
改变时。这意味着useEffect
也只在需要时运行:当getActiveUsers
更改时(即每当userId
更改时)。
linter 对这个解决方案很满意,如果您getActiveUser
向.useCallback
useEffect
Dan Abramov 的博文《完整指南》useEffect
对此进行了更详细的介绍。
推荐阅读
- c# - 反射仅在 Blazor-State-Management 中的第一次调用时成功
- node.js - 如何设置 cron 作业正确的路径来运行 node.js 脚本?
- javascript - “JSX.IntrinsicElements”类型上不存在属性“元素”
- python - 在 python3.5 中的 ImportError 上,tox 失败
- html - React Helmet 不更新元标记
- flutter - 防火墙 chrome flutter-web
- javascript - 测试从回调中调用的 window.open 是否已执行
- karate - 空手道:dynamic-csv.feature 可以用于 Get Request,如何从 CSV 传递动态路径参数?
- javascript - 如何增加全局变量 onmousehover,但让它返回到正常值 onmouseout?
- c# - CefSharp Javascript异步响应太快