useCallback理解

87 阅读3分钟

code


import Button from './Button';

export default function App() {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);

  const handleClickButton1 = () => {
    setCount1(count1 + 1);
  };

  const handleClickButton2 = useCallback(() => {
    setCount2(count2 + 1);
  }, [count2]);

  return (
    <div>
      <div>
        <Button onClickButton={handleClickButton1}>Button1</Button>
      </div>
      <div>
        <Button onClickButton={handleClickButton2}>Button2</Button>
      </div>
    </div>
  );
}
复制代码
import React from 'react';

const Button = ({ onClickButton, children }) => {
  return (
    <>
      <button onClick={onClickButton}>{children}</button>
      <span>{Math.random()}</span>
    </>
  );
};

export default React.memo(Button);

复制代码
  • 点击 Button1 的时候只会更新 Button1
  • 点击 Button2 会将两个按钮后的内容都更新;

React.memo

React.memo是React中的一个高阶组件,它可以用来优化组件的性能。当组件的props没有变化时,React.memo会缓存组件的渲染结果,下一次props没有变化时,会直接使用缓存中的渲染结果,避免不必要的重新渲染

useCallback

`useCallback` 是 React 提供的一个 Hook,它用于优化函数组件的性能。它的作用是返回一个 memoized 的回调函数,该回调函数只会在依赖项发生变化时才会返回渲染过程中创建的新函数,搭配React.memo使用,这样可以提高函数组件的性能.
复制代码

useCallback 接受两个参数:一个回调函数和一个依赖项数组。只有当依赖项数组中的值发生变化时,才会返回一个新的回调函数,否则它会返回缓存的回调函数。这样可以确保每次渲染时都使用相同的回调函数。

分析:

  • 声明的 handleClickButton1 是直接定义了一个方法,这也就导致只要是父组件重新渲染(状态或者props更新)就会导致这里声明出一个新的方法,新的方法和旧的方法尽管长的一样,但是本质依旧是两个不同的对象(在React中,在组件的第一次渲染时,这个函数会被定义并分配一个地址。当组件发生重新渲染时,由于函数组件内部的代码会被重新执行,因此这个函数也会被重新定义并分配一个新的地址。即使在代码上两个函数长得一样,它们的地址也是不同的,因此它们是不同的函数对象。)React.memo 对比后发现对象 props 改变,就会对子组件进行重新渲染
  • 而在handleClickButton2中,使用了useCallback ,并且在第二个参数传入了一个 [count2] 变量。这里 useCallback 就会根据 count2 是否发生变化,从而决定是否返回一个新的函数,函数内部作用域也随之更新。如果count2的值没有发生改变,React就不会返回一个新的函数,直接缓存中调用上一次的函数,React.memo 对比后发现对象 props没有发生改变,就不会对子组件进行重新渲染

useCallback反向优化

const [count2, setCount2] = useState(0);

const handleClickButton1 = () => {
  setCount1(count1 + 1)
};
const handleClickButton2 = useCallback(() => {
  setCount2(count2 + 1)
}, [count2]);

return (
  <>
    <button onClick={handleClickButton1}>button1</button>
    <button onClick={handleClickButton2}>button2</button>
  </>
)

复制代码
  • useCallBack缓存函数的原理:当组件状态更新的时候,组件内定义的函数还是会被重新定义(定义函数的性能开销并不高),只不过useCallback内部会将上一次定义的函数和这次组件状态更新重新定义的函数进行比较,如果发生改变了,就返回新定义的函数(这个比较的过程有一定性能开销),如果不使用React.memo,反而性能下降(多了一个比较新旧函数的过程)
  • 在count2不发生变化时,useCallback返回的的确是缓存的旧函数,而不是新定义的函数,但新定义的函数仍然会被创建。如果不配合React.memo进行优化,那么即使函数地址没有发生改变,每次重新渲染时子组件仍然会重新渲染,从而导致性能下降。只有配合React.memo使用,才能避免不必要的子组件重新渲染,从而实现性能优化。

参考原文链接[: ](详解 React useCallback & useMemo - 掘金 (juejin.cn))