React hooks useCallback 指北

6,039 阅读2分钟

react useCallback 指北

基本使用

useCallback(callback: T, deps: DependencyList): callback

把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用。

问题

useCallback 返回的函数里面缓存了什么数据

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

class Child extends React.Component {
  shouldComponentUpdate(nextProps) {
    return nextProps.flag !== this.props.flag;
  }

  render() {
    console.log('Child render');
    return <div>Child Count: {this.props.count}</div>;
  }
}

const UseCallBack = () => {
  const [count, setCount] = useState(0);
  const [selfCount, setSelfCount] = useState(100);

  const memoizedCallback = useCallback(() => {
    console.log('count change', count, selfCount);
  }, [count]);

  return (
    <div>
      <Child count={count} flag={memoizedCallback} />
      <p>self Count:{selfCount}</p>
      <p onClick={() => setCount(count + 1)}>child count add</p>
      <p onClick={() => setSelfCount(selfCount + 1)}>self count add</p>
      <p onClick={() => memoizedCallback()}>callback click</p>
    </div>

可以看到,当我们改变 selfCount 的时候,由于我们的deps里面只监听了 count,所以返回的 memoizedCallback 是没有变化的,Child 没有 re-render,当memoizedCallback执行的时候打印的selfCount 还是之前的状态,没有发生变化。

结论

useCallback执行返回的函数,是第一个传入的第一个函数的 memoized 版本,并且会缓存 useState 的所有值,只有在 deps 发生变化,返回值才函数才会更新,其内部的 useState的 值才会更新 。

使用场景

function Counter() {
  const [count, setCount] = useState(0);
  const handleIncr = () => {
    setCount(c + 1);
  };

  return (
    <div>
      {count} <Child onClick={handleIncr} />
    </div>
  );
}

假设Child是一个非常复杂的组件,每一次点击它,我们会递增count,从而触发组将重新渲染。因为Counter每次渲染都会重新生成handleIncr,所以也会导致Child重新渲染,不管Child使用了PureComponent还是使用React.memo包装。

在这里我使用useCallback去缓存我们的 handleIncr

function Counter() {
  const [count, setCount] = useState(0);
  const handleIncr = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  return (
    <div>
      {count} <Child onClick={handleIncr} />
    </div>
  );
}

只有在count发生变化的时候才会重新生成 handleIncr,这样虽然能避免了一些性能问题,但是如果需要监听的变量过多,比如 useCallback(fn, [a, b,c, d]) 这样就会让代码变的很乱。 其实在这里我们只要改变一下思路,在setCount 这样操作的时候,不用闭包中的变量,而是使用函数,获取最新的值。

const Child = React.memo(({ onClick }) => {
  console.log('child render');
  return <div onClick={onClick}>click</div>;
});

function Counter() {
  const [count, setCount] = useState(0);
  const handleIncr = useCallback(() => {
    setCount(c => c + 1);
  }, []);

  return (
    <div>
      {count} <Child onClick={handleIncr} />
    </div>
  );
}

这样 handleIncr 在每次 Counter re-render 的时候都不会改变,并且每次操作的也是最新值