useCallback 与 useMemo

727 阅读2分钟

useCallback 与 useMemo

useCallBack

参数:
  1. 一个内联回调函数
  2. 依赖项数组
返回值:
  • 该回调函数的 memoized 版本(可以理解为该回调函数被缓存起来了)

只有当依赖项发生改变时,这个回调函数才会重新生成。

使用场景举例:

如果你需要将一个函数作为props传给子组件,并且子组件内部进行了一些优化,比如判断一下props改变时才重新渲染。那么就可以将这个函数使用 useCallback 进行缓存,传入这个函数的 memoized 版本。当父组件更新时,这个 memoized 版本的函数是不变的,因此传给子组件的函数也是不变的,子组件就不会重新渲染。

function SubCounter({ onClick, data }) {
  return <button onClick={onClick}>{data}</button>;
}
​
const MemoSubCounter = React.memo(SubCounter);
let oldData, oldAddClick;
​
function Counter1() {
  console.log("Counter1 render");
  const [name, setName] = useState("计数器");
  const [number, setNumber] = useState(0);
  
  // 有没有后面的依赖项数组很重要,否则还是会重新渲染
  const addClick = useCallback(() => {
    setNumber(number + 1);
  }, [number]);
  console.log("addClick===oldAddClick ", addClick === oldAddClick);
  oldAddClick = addClick;
​
  return (
    <div>
      <p>Counter1</p>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      <MemoSubCounter data={number} onClick={addClick} />
    </div>
  );
}

上述代码中,使用了 React.memo 优化 SubCounter 组件,生成一个新的组件,新的组件有一个特性:如果接收到的props不变,就不会重新渲染。

上面代码中,给子组件传递了一个 addClick 函数,这个函数是一个 memoized 版本,即每次父组件更新时,这个函数是不变的,除非它的依赖项 number 发生了改变。因此当我们在 input 中进行输入,通过 setName 修改 name 属性,从而触发父组件更新时,子组件收到的 addClick 函数是不变的,子组件就不会再渲染了。

useMemo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
参数:
  1. 一个内联回调函数
  2. 依赖项数组
返回值:
  • 内联回调函数的返回值的 memoized 版本

useCallback 同理,当需要传一些变量给子组件时,如果这个变量没有变化,子组件也不想更新,就可以使用

useMemo 生成 memoized 版本的变量传给子组件。

function Counter2() {
  console.log("Counter2 render");
  const [name, setName] = useState("计数器");
  const [number, setNumber] = useState(0);
  // 父组件更新时,这里的变量和函数每次都会重新创建,那么子组件接受到的属性每次都会认为是新的
  // 所以子组件也会随之更新,这时候可以用到 useMemo
  // 有没有后面的依赖项数组很重要,否则还是会重新渲染
  
  // 如果这样生成 data, 那么每次传给子组件的 data 都是不同的
  const data = { number } 
  // 如果这样生成 data, 那么每次传给子组件的 data 都是相同的,除非 number 发生改变
  const data = useMemo(() => ({ number }), [number]);
  console.log("data===oldData ", data === oldData);
  oldData = data;
​
  // 有没有后面的依赖项数组很重要,否则还是会重新渲染
  const addClick = useCallback(() => {
    setNumber(number + 1);
  }, [number]);
  console.log("addClick===oldAddClick ", addClick === oldAddClick);
  oldAddClick = addClick;
​
  return (
    <div>
      <p>Counter2</p>
      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
      />
      <MemoSubCounter data={number} onClick={addClick} />
    </div>
  );
}

演示代码:codesandbox.io/s/learn-hoo…