如何在 React 中使用 hooks 实现 componentDidMount 以符合 EsLint 规则“re​​act-hooks/exhaustive-deps”:“warn”?

Posted

技术标签:

【中文标题】如何在 React 中使用 hooks 实现 componentDidMount 以符合 EsLint 规则“re​​act-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 更改,userIduserData 将不同步。也许这是您的意图,但就 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 规则“re​​act-hooks/exhaustive-deps”:“warn”?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 React Hooks 实现复杂组件的状态管理

React Hook 功能组件防止重新渲染

如何写自定义 React Hook?

React-Apollo-Hooks 使用Mutation 传递空变量?

如何将 redux-sagas 与 react-hooks 一起使用

使用 Jest 和 React 测试库测试 Apollo React Hooks