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"?
根据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规则是指effect里面的数组不能再为空了?
声明 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 或状态而引起的问题。虽然 getActiveUser
本身不是 prop 或 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 规则。
但假设这两种情况都不是...
一个non-solution(效果太多)
如您所述,将 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 仍然不高兴:它不够聪明知道我们已经修复了对 getActiveUser
的依赖取决于 getActiveUser
所依赖的所有事物。
这很脆弱:如果您在将来添加 getActiveUser
依赖的 prop 或状态,而忘记在此处添加,您将遇到过时的数据问题。
更好的解决方案
所以推荐的方案是:
const MyComponent = ({userId}) => {
const getActiveUsers = useCallback(() => {
// uses userId
}, [userId]);
useEffect(() => {
getActiveUser();
}, [getActiveUsers]) // No error
return <></>;
}
通过将 getActiveUsers
包裹在 useCallback
中,函数实例仅在需要时被替换:当 userId
发生变化时。这意味着 useEffect
也仅在需要时 运行s:当 getActiveUsers
更改时(即每当 userId
更改时)。
linter 对这个解决方案很满意,如果你向 getActiveUser
引入新的依赖项,你只需要更改它的 useCallback
依赖,而不是 useEffect
。
Dan Abramov 的博文 A Complete Guide to useEffect
对此进行了更详细的介绍。
根据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规则是指effect里面的数组不能再为空了?
声明 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 或状态而引起的问题。虽然 getActiveUser
本身不是 prop 或 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 规则。
但假设这两种情况都不是...
一个non-solution(效果太多)
如您所述,将 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 仍然不高兴:它不够聪明知道我们已经修复了对 getActiveUser
的依赖取决于 getActiveUser
所依赖的所有事物。
这很脆弱:如果您在将来添加 getActiveUser
依赖的 prop 或状态,而忘记在此处添加,您将遇到过时的数据问题。
更好的解决方案
所以推荐的方案是:
const MyComponent = ({userId}) => {
const getActiveUsers = useCallback(() => {
// uses userId
}, [userId]);
useEffect(() => {
getActiveUser();
}, [getActiveUsers]) // No error
return <></>;
}
通过将 getActiveUsers
包裹在 useCallback
中,函数实例仅在需要时被替换:当 userId
发生变化时。这意味着 useEffect
也仅在需要时 运行s:当 getActiveUsers
更改时(即每当 userId
更改时)。
linter 对这个解决方案很满意,如果你向 getActiveUser
引入新的依赖项,你只需要更改它的 useCallback
依赖,而不是 useEffect
。
Dan Abramov 的博文 A Complete Guide to useEffect
对此进行了更详细的介绍。