React Hooks中useCallback和useMemo的作用

useCallback

useCallback会返回一个 memoized 的函数。看起来它是用来对函数进行缓存,防止总是重复的生成新的函数。

对函数缓存,这到底有什么用处呢?

export function Demo() {
  const [value, setValue] = useState(0);

  const changeValue = () => {
    setValue(value + 1);
  };

  return (
    <div>
      {/*SomeComplexComponent是一个十分复杂的组件*/}
      <SomeComplexComponent onIncrement={changeValue} />
    </div>
  );
}

假如SomeComplexComponent是一个十分复杂的组件,如果changeValue变化了,势必会导致SomeComplexComponent这个组件重新渲染,这就带来了额外的性能浪费,因为前后这个组件并没有发生变化。如果使用class风格的 React,可以在shouldComponentUpdate进行判断,避免不必要渲染,但是,在这里的函数中不存在shouldComponentUpdate钩子,还好,React 提供了useCallback这个 hook,借助这个 hook 也可以做到shouldComponentUpdate同样的操作。使用useCallback来对changeValue进行缓存,保证每次都返回是同一个函数,就避免了SomeComplexComponent组件的重新渲染。

既然使用useCallback能对函数进行缓存操作,我们怎么去验证每次的函数都是相同的呢?

可以使用Set将每次生成的函数保存起来,看最终Set的长度,如果一直是1就能说明每次的函数都是同一个函数。

const set = new Set();
export function Demo() {
  const [value, setValue] = useState(0);

  const changeValue = useCallback(() => {
    setValue(value + 1);
  }, []);

  const [count, setCount] = useState(0);

  set.add(changeValue);
  console.log(set.size);

  return (
    <div>
      {/*SomeComplexComponent是一个十分复杂的组件*/}
      <SomeComplexComponent onIncrement={changeValue} />
      <button onClick={() => setCount(count + 1)}>count++</button>
    </div>
  );
}

这里用count触发重新渲染,结果在控制台看到打印的长度始终是1。如果把useCallback去除,你会看到长度会进行递增。就是说,useCallback确实使函数返回的都是同一个,既然值没有变化,就不会导致SomeComplexComponent重新渲染。

但是,上述示例还是有 bug,对代码做一些修改,点击按钮对value进行递增操作。结果,value的值始终是1,并没有继续递增了。

export function Demo() {
  const [value, setValue] = useState(0);

  const changeValue = useCallback(() => {
    setValue(value + 1);
  }, []);

  return (
    <div>
      {/*SomeComplexComponent是一个十分复杂的组件*/}
      <SomeComplexComponent onIncrement={changeValue} />
      {value}
      <button onClick={changeValue}>count++</button>
    </div>
  );
}

可以看到value的值始终为1。这是什么情况?

这是因为useCallback的第二个参数没有指定,它只会在第一次时进行changeValue闭包缓存,所以初始是0,缓存后就一直为0,执行一次自增,所以值始终是1

解决办法很简单,指定useCallback的依赖即可,当依赖发生变化时,其自动更新缓存。

const changeValue = useCallback(
  () => {
    setValue(value + 1);
  },
  [value]
);

useMemo

这个 hook 表示对函数求值进行缓存,当依赖没有发生变化时,不执行计算,直接返回缓存结果(有点像 vue 的计算属性)。对于一些复杂耗时的计算,使用它能够优化程序的性能。

使用起来和useCallback类似,只不过useMemo返回的是一个值。

const newValue = useMemo(() => Math.sqrt(value), [value]);

总结

这两个 hook 的设计目的都是用来缓存操作的。useCallback用来缓存函数,useMemo用来缓存计算结果。当你的程序出现一些重复渲染和频繁计算操作时,就可以考虑用这两个 hook 来优化了。

如果您觉得本文对您有用,欢迎捐赠或留言~
微信支付
支付宝

发表评论

您的电子邮箱地址不会被公开。 必填项已用 * 标注