useMemo hooks介绍

useMemo hooks的语法如下

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

该hooks返回一个记忆过的值,也就是计算过的值

把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。

传入 useMemo 的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴,而不是 useMemo。

你可以把 useMemo 作为性能优化的手段,但不要把它当成语义上的保证。将来,React 可能会选择“遗忘”以前的一些 memoized 值,并在下次渲染时重新计算它们,比如为离屏组件释放内存。先编写在没有 useMemo 的情况下也可以执行的代码 —— 之后再在你的代码中添加 useMemo,以达到优化性能的目的。

推荐启用 eslint-plugin-react-hooks 中的 exhaustive-deps 规则。此规则会在添加错误依赖时发出警告并给出修复建议。

从上面可以看出,react useMemo hooks可以用来优化函数组件的运行效率

不同于 useCallback , useMemo 是用来“记忆”值,useCallback 用来“记忆”函数,React.memo用来包裹函数组件,避免重新渲染。

接下来通过实际例子来演示,比如我们有一个筛选列表,我们可以会这样实现

const Content = () => {
  const [keyword, setKeyword] = useState("");

  const filterList = initList.filter((i) => {
    console.log("filter handle");
    return new RegExp(keyword, "ig").test(i.name);
  });

  const handleKeywordChange = (e) => {
    setKeyword(e.target.value);
  };

  return (
    <div>
      <div>
        <input
          value={keyword}
          type="text"
          placeholder="搜索"
          onChange={handleKeywordChange}
        />
      </div>
      <ul>
        {filterList.map((i) => (
          <li key={i.id}>{i.name}</li>
        ))}
      </ul>
    </div>
  );
};

看起来功能没什么毛病吧,但是正常的功能页面不止一个状态,我们比如新增一个loading字段,这里例子当作只是用来演示多个状态如何影响性能

const Content = () => {
  const [loading, setLoading] = useState(false);
  const [keyword, setKeyword] = useState("");

  const filterList = initList.filter((i) => {
    console.log("filter handle");
    return new RegExp(keyword, "ig").test(i.name);
  });

  const handleKeywordChange = (e) => {
    setKeyword(e.target.value);
  };

  return (
    <div>
      <div>
        <input
          value={keyword}
          type="text"
          placeholder="搜索"
          onChange={handleKeywordChange}
        />
        <button 
          onClick={() => {
            setLoading(!loading);
          }}
        >
          搜索
        </button>
      </div>
      <ul>
        {loading && <span>loading...</span>}
        {filterList.map((i) => (
          <li key={i.id}>{i.name}</li>
        ))}
      </ul>
    </div>
  );
};

可以看出,每次更新loading值,filter计算都会重新被调用(通过console可以看出),这时候是不是就浪费了大量不必要都重新计算,这时候useMemo就可以派上用场了,只需要做下改动即可

const Content = () => {
  const [loading, setLoading] = useState(false);
  const [keyword, setKeyword] = useState("");

  const filterList = useMemo(() => {
    return initList.filter((i) => {
      console.log("filter handle");
      return new RegExp(keyword, "ig").test(i.name);
    });
  }, [keyword]);

  const handleKeywordChange = (e) => {
    setKeyword(e.target.value);
  };

  return (
    <div>
      <div>
        <input
          value={keyword}
          type="text"
          placeholder="搜索"
          onChange={handleKeywordChange}
        />
        <button
          onClick={() => {
            setLoading(!loading);
          }}
        >
          搜索
        </button>
      </div>
      <ul>
        {loading && <span>loading...</span>}
        {filterList.map((i) => (
          <li key={i.id}>{i.name}</li>
        ))}
      </ul>
    </div>
  );
};

这时候如果重新改变loading状态,我们可以看到filter不会重新计算了。是不是很happy。

具体效果可以通过以下代码来演示