React 函数式组件优化(续)

844 阅读3分钟

前言

在之前的一篇文章React 函数式组件优化中,提到可以利用 useCallback 来将函数记忆化,以达到每次传给子组件的都是同一个函数,避免子组件函数重复执行,提升性能。

针对函数的记忆化,除了使用 useCallback 之外,还能使用 useMemo

useMemo

useMemo 的用法与 useCallback 类似,但 useCallback 是直接传入需要记忆化的函数,而 useMemo 是传入一个函数,这个传入的函数返回你需要记忆化的函数,示例如下:

const childOnClick = React.useMemo(() => {
  return () => {
    console.log(`m: ${m}`);
  };
}, [m]);

完整代码点这里,效果如下:

useMemo-demo1.gif

乍一看两者实现的功能都一样,用 useMemo 相比 useCallback 还需要多写一层函数,那 useMemo 相比 useCallback 有什么优势呢?

数据记忆化

  • 对象记忆化

在实际的工作中,我们还会遇到一种情况,就是传递给子组件的不是一个函数,而是一个对象数据,如果对象数据没做记忆化处理,那么即使对象的具体数据没变,但每次都是一个新地址的对象,也会被认为是一个新对象,引发子组件函数重新执行。

import * as React from "react";
import { render } from "react-dom";

import "./styles.css";

function App() {
  const [n, setN] = React.useState(0);
  const [m, setM] = React.useState(0);
  const data = { m: m };
  return (
    <div className="App">
      <div>React.useMemo demo-3</div>
      <button
        onClick={() => {
          setN(n + 1);
        }}
      >
        update n: {n}
      </button>
      <button
        onClick={() => {
          setM(m + 1);
        }}
      >
        update m: {m}
      </button>
      <Child data={data} />
    </div>
  );
}

const Child = React.memo((props: { data: { m: number } }) => {
  console.log("render child");
  return (
    <div className="child">
      <div>m:{props.data.m}</div>
      <div>Child Component</div>
    </div>
  );
});

const rootElement = document.getElementById("root");
render(<App />, rootElement);

也可以点击这里查看,具体效果如下:

useMemo-demo2.gif

useCallback 无法记忆化对象数据,那这时候就可以利用 useMemo 对数据进行记忆化。

const data = React.useMemo(() => {
  return { m: m };
}, [m]);

完整代码点这里,具体效果如下:

useMemo-demo3.gif

  • 计算结果记忆化

useMemo 除了可以对对象进行记忆化,还可以对基本类型的数据进行记忆化,比如对需要进行大量计算才能得到的结果进行记忆化,避免组件更新时进行重复的大量计算。

看一个例子。

import * as React from "react";
import { render } from "react-dom";

import "./styles.css";

function App() {
  const [n, setN] = React.useState(1);
  const [m, setM] = React.useState(0);
  const factorial = React.useMemo(() => {
    console.log("计算了一次阶乘");
    let result = 1;
    for (let i = 1; i <= n; i++) {
      result = result * i;
    }
    return result;
  }, [n]);
  return (
    <div className="App">
      <div>React.useMemo demo-4</div>
      <div>
        {n}的阶乘是:{factorial}
      </div>
      <button
        onClick={() => {
          setN(n + 1);
        }}
      >
        update n: {n}
      </button>
      <button
        onClick={() => {
          setM(m + 1);
        }}
      >
        update m: {m}
      </button>
    </div>
  );
}

const rootElement = document.getElementById("root");
render(<App />, rootElement);

完整代码点这里,效果如下:

useMemo-demo4.gif

可以看到,useMemo 的依赖数组只包含 n,当 m 更新时,直接返回记忆化的值,所以阶乘就没有再次计算;而更新了 n 时,之前记忆的数字就需要再次更新,这时才再次计算阶乘。

总结

利用 useMemo 我们不仅可以记忆化函数,还可以记忆化数值,避免函数重复计算,提升性能。

需要注意的是我们使用这些 Hooks 来进行优化时,本身也是会消耗性能的,如果优化过程消耗的性能大于优化节约的性能,那就得不偿失了。