如何避免useEffect deps中没有保证的useCallback deps出现问题?

时间:2019-03-29 00:49:14

标签: react-hooks

我最近开始使用新的React Hooks API,发现它很棒!

但是,我在依赖项区域遇到了一个小混乱。

这是怎么回事?

基本上,我的用例非常简单,可以通过以下伪代码进行说明:

import React, { useState, useCallback, useEffect } from 'react'

function Component() {
  const [state, setState] = useState()

  const doStuff = useCallback(() => {
    // Do something 
    setState(result)
  }, [setState])

  useEffect(() => {
    // Do stuff ONLY at mount time
    doStuff()
  }, [])

  return <ExpensivePureComponent doStuff={doStuff} />
}

现在,以上代码可以正常工作。

但是在我安装eslint-plugin-react-hooks之后,出现了警告。我必须声明在我的效果中使用的所有依赖项,即doStuff

好的,让我们修复该代码:

  useEffect(() => {
    // Do stuff ONLY at mount time
    doStuff()
  }, [doStuff])

酷,不再警告!

等等,没有警告,但是...也没有错误?

让我们看看文档对useCallback的评价:

  

useCallback(fn,deps)等同于useMemo(()=> fn,deps)

然后,大约useMemo

  

您可以将useMemo用作性能优化,而不是语义保证。将来,React可能会选择“忘记”一些以前记忆的值,并在下一次渲染时重新计算它们

因此,基本上,我的doStuff回调(也就是我的useEffect)是否不再保证仅在安装时运行?那不是问题吗?

我了解eslint插件的原理,但在我看来useCalback / useMemo依赖项数组与useEffect的依赖项数组之间存在危险的混淆,否则我是否会错过什么?

可能是因为甚至文档都说我的最终代码很好:

  

如果由于某种原因您无法在效果内移动功能,则还有更多选择:

     

●...

     

●作为最后的选择,您可以添加一个函数来实现依赖关系,但将其定义包装到useCallback挂钩中。 这确保除非每个渲染器的依赖关系也发生变化,否则它不会在每个渲染器上都发生变化


我:等等等等钩了等等

SO:您的问题是什么? :)

好吧,您怎么看?代码安全吗?文档说是,但是也说不是,因为不能保证回调不会改变……这有点令人困惑。

上述伪代码是否存在不良做法?如果无法避免这种情况,该怎么办? // eslint-disable-next-line

1 个答案:

答案 0 :(得分:1)

虽然文档说的是

useCallback(fn, deps) is equivalent to useMemo(() => fn, deps)

这并不意味着useCallback是使用useMemo实现的,当然不是。因此,尽管useMemo可能选择重新计算,但除非依赖项数组中的某些内容发生更改,否则useCallback不会更新该函数。

此外,由于useState返回的设置器没有更改,因此您无需将其传递给useCallback

  const doStuff = useCallback(() => {
    // Do something 
    setState(result)
  }, [])

由于doStuff不会更改,因此useEffect除了初始安装外不会再被调用。

然而,在使用useEffectuseCallback时要牢记的一件事是,如果useCallback中的依赖项数组发生更改,则将重新创建回调,因此useEffect将重新运行。防止这种情况的一种方法是使用useReducer钩子而不是useState并依靠dispatch来更新状态,因为状态在您与应用程序交互期间不会改变。一个会话。

import React, { useReducer, useEffect } from 'react'

const initialState = [];
const reducer = (state, action) => {
    switch(action.type) {
        case 'UPDATE_STATE' : {
            return action.payload
        }
        default: return state;
    }
}
function Component() {
  const [state, dispatch] = useReducer(reducer, initialState);


  useEffect(() => {
    // Do stuff ONLY at mount time
    dispatch({type: 'UPDATE_RESULT', payload: ['xyz']})
  }, [])

  return <ExpensivePureComponent dispatch={dispatch} />
}