useCallback 和 useMemo 是个坑,千万别使用(一)

241 阅读2分钟

前言

去掉所有的 useCallback 和 useMemo。 90% 的时间你不需要它。

useCallback

useCallback 是 React 钩子,可以创建对 JavaScript 的引用,这有助于 React 的渲染引擎了解对象没有改变。

例如:

function FidgetSpinner() {
    const [spinning, setSpinning] = useState(false)


    const newFuncEveryTime = () => {
        setSpinning(!spinning)
    }


    const stableFunc = useCallback(() => {
        setSpinning(!spinning)
    }, [spinning])


    return (
        <>
            <p>Is it spinning? {spinning}</p>
            <Spinner spinning={spinning} onClick={...} />
        </>
    )
}

 <Spinner> 呈现一个旋转或不旋转。 onClick 属性接受更新 spinning 状态的函数。

引用稳定性和重新渲染

React 使用 prop 值来决定何时重新渲染 <Spinner> 组件。当值改变时,组件重新渲染。

对于函数和其他对象,“值”是它们的内存地址。即使函数或对象看起来相同,但拥有了新地址,React 也会认为它不同并重新渲染您的组件。

这就是 useCallback 的用武之地。

const newFuncEveryTime = () => {
  setSpinning(!spinning)
}

任何时候 React 的接触 <FidgetSpinner> 组件(调用该函数)时,这都是一个新函数。无论是否更新 DOM,调用组件都会使用新的内存地址重新定义此函数。这会导致重新渲染 <Spinner> 。

const stableFunc = useCallback(() => {
  setSpinning(!spinning)
}, [spinning])

这将创建一个稳定的内存地址的‘记忆函数’。它仅在依赖项的数组更改时创建新函数。

在这种情况下,只要 spinning 的值发生变化, useCallback 就会创建一个全新的函数。

缺点

错误的依赖数组会造成一个 JavaScript 闭包问题。

如果像这样定义它:

const stableFunc = useCallback(() => {
  setSpinning(!spinning)
}, [])

spinning 值被“永远”地嵌入到函数中。调用此函数不会将 spinning 从 false 切换为 true ,它始终会将其设置为 true。(如果初始值为真,则为假。)

这是一个大问题。

为什么不建议使用useCallback

  • useCallback 引入了内存开销。 JavaScript 机制需要保留所有这些记忆函数的堆栈,并将其携带到组件所在的任何地方。做得太多或弄错了,这会导致内存泄漏和陈旧的渲染。这是一个大问题。
  • 遇到无限循环。当您使用不稳定的回调或对象作为 useEffect 依赖项时,就会发生这种情况。每次渲染都重新定义回调,触发效果,导致重新渲染,这......😬
  • 还有人会有这样的写法,千万不要这样做,去定义一个组件吧。
function Component() {
  const SubComponent = useCallback(() => {
    return <div>This is a component damn it!</div>
  }, [])


  return (
    <>
      <p>Lorem Ipsum</p>
      {SubComponent()}
    </>
  )
}

上篇文完

谢谢!

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 20 天

点击查看活动详情