对 react hooks-useCallback和useMemo 的理解

通常情况下,React 会有多余的 render。

1. 常用的场景一:子组件依赖父组件数据,当父组件数据更新时,会重新渲染子组件。

// index.tsx
function Index() {
  const [count, setCount] = useState(0);

  const handleAdd = () => {
    setCount((i) => i + 1);
  };

  return (
    <div className="container">
      <h2>{count}</h2>
      <Button type="primary" size="large" onClick={handleAdd}>
        点击 +1
      </Button>
      <Child data={count} />
    </div>
  );
}

// child.tsx
function Child(props: any) {
  console.log("child 被渲染了!!");

  return (
    <div className="container">
      <Card>child 被渲染了({props.data})</Card>
    </div>
  );
}

2. 常见的场景二:子组件依赖父组件数据,当父组件其他数据(非子组件依赖的数据)更新时,子组件也会重新渲染!

// index.tsx
// const data = 0; // 定义一个常量
function Index() {
  const [count, setCount] = useState(0);
  const [data, setData] = useState(0);

  const handleAdd = () => {
    setCount((i) => i + 1);
  };

  return (
    <div className="container">
      <h2>{count}</h2>
      <Button type="primary" size="large" onClick={handleAdd}>
        点击 +1
      </Button>
      <Child data={data} />
    </div>
  );
}

// child.tsx
function Child(props: any) {
  console.log("child 被渲染了!!");

  return (
    <div className="container">
      <Card>child 被渲染了({props.data})</Card>
    </div>
  );
}

3. 常见的场景三:子组件不依赖父组件数据,当父组件数据更新时,子组件也会重新渲染!!

// index.tsx
function Index() {
  const [count, setCount] = useState(0);

  const handleAdd = () => {
    setCount((i) => i + 1);
  };

  return (
    <div className="container">
      <h2>{count}</h2>
      <Button type="primary" size="large" onClick={handleAdd}>
        点击 +1
      </Button>
      <ChildPure />
    </div>
  );
}

// child.tsx
function ChildPure() {
  console.log("childPure 被渲染了!!");

  return (
    <div className="container">
      <Card>childPure 被渲染了</Card>
    </div>
  );
}

【总结】不论子组件是否有依赖父组件的数据,当父组件数据更新时(也就是调用 setXXX()),react 会重新渲染整个视图,其中包括了子组件。多数情况下,这种渲染组件操作是多余的、浪费的,react.memo() 可以用来解决这个问题。

react.memo

将代码里的 Child 用 React.memo(Child) 包裹,当 props 没有更新时,React 会跳过渲染组件的操作并直接服用最近一次渲染的结果。

// index.tsx
function Index() {
  const [count, setCount] = useState(0);

  const handleAdd = () => {
    setCount((i) => i + 1);
  };

  return (
    <div className="container">
      <h2>{count}</h2>
      <Button type="primary" size="large" onClick={handleAdd}>
        点击 +1
      </Button>
      <ChildPureMemo />
    </div>
  );
}

// child.tsx
function ChildPure() {
  console.log("childPure 被渲染了!!");

  return (
    <div className="container">
      <Card>childPure 被渲染了</Card>
    </div>
  );
}

export const ChildPureMemo = React.memo(ChildPure);

但是这里还有个bug,当给组件添加了监听事件后,又回到了最初的重复渲染状态。

// index.tsx
function Index() {
  const [count, setCount] = useState(0);

  const handleAdd = () => {
    setCount((i) => i + 1);
  };

  const handleData = () => {};

  return (
    <div className="container">
      <h2>{count}</h2>
      <Button type="primary" size="large" onClick={handleAdd}>
        点击 +1
      </Button>
      <ChildMemo data={data} onClick={handleData} />
    </div>
  );
}

// child.tsx
function Child(props: any) {
  console.log("child 被渲染了!!");

  return (
    <div className="container">
      <Card onClick={props.onClick}>child 被渲染了({props.data})</Card>
    </div>
  );
}

export const ChildMemo = React.memo(Child);

[总结] 使用 react.memo() 包裹的子组件,当 props 没有变化时,不会重新渲染子组件。但是监听事件在父组件重新渲染时,虽然功能一样,但引用地址变化了!!所以对于子组件来说,props发生了变化,进行重新渲染。这种情况下,需要使用 useCallback 解决。

useCallback

useCallback 的第二个参数是依赖项,当依赖发生变化时,才会重新计算新的value,如果依赖不变,就重用之前的value。

// index.tsx
function Index() {
  const [count, setCount] = useState(0);

  const handleAdd = () => {
    setCount((i) => i + 1);
  };

  const handleData = useCallback(() => {
    console.log(111);
    setData((i) => i + 1);
  }, []);

  return (
    <div className="container">
      <h2>{count}</h2>
      <Button type="primary" size="large" onClick={handleAdd}>
        点击 +1
      </Button>
      <ChildMemo data={data} onClick={handleData} />
    </div>
  );
}

// child.tsx
function Child(props: any) {
  console.log("child 被渲染了!!");

  return (
    <div className="container">
      <Card onClick={props.onClick}>child 被渲染了({props.data})</Card>
    </div>
  );
}

export const ChildMemo = React.memo(Child);

useMemo

useMemo 的用途和 useCallback 一样,只是第一个参数的类型不一样。
useCallback(value, []) => useMemo(()=>value,[]) 如果 value 是一个函数时,useMemo 的写法会相对不易理解

// index.tsx
function Index() {
  const [count, setCount] = useState(0);

  const handleAdd = () => {
    setCount((i) => i + 1);
  };

  const handleData = useMemo(
    () => () => {
      console.log(111);
      setData((i) => i + 1);
    },
    []
  );

  return (
    <div className="container">
      <h2>{count}</h2>
      <Button type="primary" size="large" onClick={handleAdd}>
        点击 +1
      </Button>
      <ChildMemo data={data} onClick={handleData} />
    </div>
  );
}

// child.tsx
function Child(props: any) {
  console.log("child 被渲染了!!");

  return (
    <div className="container">
      <Card onClick={props.onClick}>child 被渲染了({props.data})</Card>
    </div>
  );
}

export const ChildMemo = React.memo(Child);

官方文档

1. react.memo

2. useCallback

3. useMemo

参考文档

1. 浅谈 Hooks

posted @ 2022-03-11 13:42  shellon  阅读(85)  评论(0编辑  收藏  举报