首页 > 解决方案 > 如何避免 useEffect 部门中未保证的 useCallback 部门的问题?

问题描述

我最近开始使用新的 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回调,因此 my useEffect,不再保证只在挂载时运行?这不是问题吗?

我了解 eslint 插件背后的原理,但在我看来useCalback/useMemo依赖数组和useEffect一个数组之间存在危险的混淆,或者我错过了什么?

可能是因为即使是文档也说我的最终代码很好:

如果由于某种原因您无法在效果内移动函数,还有其他一些选项:

● ...

● 作为最后的手段,您可以添加一个函数来影响依赖关系,但将其定义包装到 useCallback Hook 中。这确保它不会在每次渲染时都发生变化,除非它自己的依赖项也发生变化


我:等等等等钩子布拉

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

那么你觉得呢?代码安全吗?文档说是,但也说不是,因为不能保证回调不会改变......这有点令人困惑。

上面的伪代码有不好的做法吗?当这种模式无法避免时,该怎么办?// eslint-disable-next-line?

标签: react-hooks

解决方案


虽然文档说

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

这并不意味着 useCallback 是实现 using useMemo,当然不是。因此,虽然 useMemo 可能会选择再次计算,但 useCallback 不会更新函数,除非依赖数组中的某些内容发生变化。

此外,由于返回的 setteruseState不会更改,因此您无需将其传递给useCallback

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

由于 doStuff 不会更改,useEffect因此除了初始挂载外不会再次调用。

useEffect但是,在使用时你应该记住的一件事useCallback是,如果你在 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} />
}

推荐阅读