重学React系列-useCallback、useMemo及memo

964 阅读2分钟

react性能优化必备知识点

一. react更新特性——子组件会跟随父组件而更新

因为react的diff算法同层比较特性,子组件会默认随父组件而更新。正因为如此特性,react很容易出现性能瓶颈。

import "./styles.css";
import { useState } from "react";
import Child from "./child";

function App() {
  const [count, setCount] = useState(false);
  const [count2, setCount2] = useState(false);
  return (
    <div className="App">
      <Child></Child>
      <button
        onClick={() => {
          setCount(!count);
        }}
      >
        count:{count.toString()}
      </button>
      <button
        onClick={() => {
          setCount2(!count2);
        }}
      >
        count2:{count2.toString()}
      </button>
    </div>
  );
}

function Child() {
   return <div>{Math.random()}</div>;
}

可以预见以上代码,无论点击哪个button子组件的随机都会发生改变,即使子组件为无状态组件。

二. memo

React.memo为高级组件(即接受的参数为函数)。如果你的组件在相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用。React.memo 仅检查 props 变更,当props变动则会重新渲染,反之则可避免重复渲染导致性能浪费。

import { memo } from "react";

function Child() {

   return <div>{Math.random()}</div>;

}
export default memo(Child);

将上例代码稍加修改,将Child组件用react.memo包裹,即可以避免重复的渲染,达到一定的性能优化。

三. useCallback

众所周知, ()=>{} === ()=>{} 输出false,正如memo的属性声明一样,memo能检查props的更新,但如果当props包含函数时,memo将无法正常判断props是否变动了。

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

import "./styles.css";
import { useCallback, useState, memo } from "react";
import Child from "./child";

export default function App() {
  const [count, setCount] = useState(false);
  const [count2, setCount2] = useState(false);

  const onClickChild = useCallback(() => {}, [count]);
  return (
    <div className="App">
      <Child onClick={onClickChild}></Child>
      <button
        onClick={() => {
          setCount(!count);
        }}
      >
        count:{count.toString()}
      </button>
      <button
        onClick={() => {
          setCount2(!count2);
        }}
      >
        count2:{count2.toString()}
      </button>
    </div>
  );
}

当点击button1,count被改变,因为useCallBack的绑定,Child被重新渲染。点击button2,Child则不会发生任何改变。

四. useMemo

先上反例:

import "./styles.css";
import {  useState } from "react";
import Child from "./child";

export default function App() {
  const [count, setCount] = useState(false);
  const [count2, setCount2] = useState(false);

  const useInfo = {
    name: "name",
    count: count
  };
  return (
    <div className="App">
      <Child useInfo={useInfo}></Child>
      <button
        onClick={() => {
          setCount(!count);
        }}
      >
        count:{count.toString()}
      </button>
      <button
        onClick={() => {
          setCount2(!count2);
        }}
      >
        count2:{count2.toString()}
      </button>
      <div>
        <input
          placeholder="请输入"
          onChange={() => {
            setCount2(!count2);
          }}
        ></input>
      </div>
    </div>
  );
}

当输入框输入值时,即使Child的props并没有与count2有任何关系,但依旧伴随着输入框的输入重渲染,导致了无谓的浪费。useMemo Hook 允许你通过「记住」上一次计算结果的方式在多次渲染的之间缓存计算结果。使用useMemo优化:

...
const useInfo = useMemo(
    () => ({
        count: count,
        name: "name"
    }),
    [count]
);
...

五. 结论

useMemo以及useCallback并非万能性能优化的api。需要明确的是,当父组件的重渲染强度并不高时盲目使用useMemo以及useCallback不但起不到优化作用,反而会产生负优化。

六. 代码库

codesandbox.io/s/boring-rh…