React中memo()、useCallback()、useMemo() 区别使用详解

React.memo()

React 中当组件的 props 或 state 变化时,会重新渲染视图,实际开发会遇到不必要的渲染场景。看个例子

子组件:

function ChildComp () {
  console.log('render child-comp ...')
  return <div>Child Comp ...</div>
}

父组件:

function ParentComp () {
  const [ count, setCount ] = useState(0)
  const increment = () => setCount(count + 1)

  return (
    <div>
      <button onclick="{increment}">&#x70B9;&#x51FB;&#x6B21;&#x6570;&#xFF1A;{count}</button>
      <childcomp>
    </childcomp></div>
  );
}

子组件中有条 console 语句,每当子组件被渲染时,都会在控制台看到一条打印信息。

点击父组件中按钮,会修改 count 变量的值,进而导致父组件重新渲染,此时子组件压根没有任何变化(props、state),但在控制台中仍然看到子组件被渲染的打印信息。

我们期待的结果:子组件的 props 和 state 没有变化时,即便父组件渲染,也不要渲染子组件。

1、修改子组件,用 React.memo() 包一层。

这种写法是 React 的高阶组件写法,将组件作为函数(memo)的参数,函数的返回值(ChildComp)是一个新的组件。

import React, { memo } from 'react'

const ChildComp = memo(function () {
  console.log('render child-comp ...')
  return <div>Child Comp ...</div>
})

觉得上面👆那种写法别扭的,可以拆开写。

import React, { memo } from 'react'

let ChildComp = function () {
  console.log('render child-comp ...')
  return <div>Child Comp ...</div>
}

ChildComp = memo(ChildComp)

此时再次点击按钮,可以看到控制台没有打印子组件被渲染的信息了。

React.useCallback()

上面的例子中,父组件只是简单调用子组件,并未给子组件传递任何属性。
看一个父组件给子组件传递属性的例子:

子组件:(子组件仍然用 React.memo() 包裹一层)

import React, { memo } from 'react'

const ChildComp = memo(function ({ name, onClick }) {
  console.log('render child-comp ...')
  return <>
    <div>Child Comp ... {name}</div>
    <button onclick="{()" => onClick('hello')}>&#x6539;&#x53D8; name &#x503C;</button>
  </>
})

父组件:

function ParentComp () {
  const [ count, setCount ] = useState(0)
  const increment = () => setCount(count + 1)

  const [ name, setName ] = useState('hi~')
  const changeName = (newName) => setName(newName)  // &#x7236;&#x7EC4;&#x4EF6;&#x6E32;&#x67D3;&#x65F6;&#x4F1A;&#x521B;&#x5EFA;&#x4E00;&#x4E2A;&#x65B0;&#x7684;&#x51FD;&#x6570;

  return (
    <div>
      <button onclick="{increment}">&#x70B9;&#x51FB;&#x6B21;&#x6570;&#xFF1A;{count}</button>
      <childcomp name="{name}" onclick="{changeName}/">
    </childcomp></div>
  );
}

父组件在调用子组件时传递了 name 属性和 onClick 属性,此时点击父组件的按钮,可以看到控制台中打印出子组件被渲染的信息。

分析下原因:

点击父组件按钮,改变了父组件中 count 变量值(父组件的 state 值),进而导致父组件重新渲染;父组件重新渲染时,会重新创建 changeName 函数,即传给子组件的 onClick 属性发生了变化,导致子组件渲染;由于子组件的 props 改变了,所以子组件渲染了,但是我们只是点击了父组件的按钮,并未对子组件做任何操作,压根就不希望子组件的 props 有变化。

useCallback 钩子进一步完善这个缺陷。

修改父组件的 changeName 方法,用 useCallback 钩子函数包裹一层。

// &#x6BCF;&#x6B21;&#x7236;&#x7EC4;&#x4EF6;&#x6E32;&#x67D3;&#xFF0C;&#x8FD4;&#x56DE;&#x7684;&#x662F;&#x540C;&#x4E00;&#x4E2A;&#x51FD;&#x6570;&#x5F15;&#x7528;
  const changeName = useCallback((newName) => setName(newName), [])

此时点击父组件按钮,控制台不会打印子组件被渲染的信息了。

究其原因:useCallback() 起到了缓存的作用,即便父组件渲染了,useCallback() 包裹的函数也不会重新生成,会返回上一次的函数引用。

React.useMemo()

前面父组件调用子组件时传递的 name 属性是个字符串,如果换成传递对象会怎样?

下面例子中,父组件在调用子组件时传递 info 属性,info 的值是个对象,点击父组件按钮时,发现控制台打印出子组件被渲染的信息。

分析原因跟调用函数是一样的:

点击父组件按钮,触发父组件重新渲染;父组件渲染,const info = { name, age } 一行会重新生成一个新对象,导致传递给子组件的 info 属性值变化,进而导致子组件重新渲染。

使用 useMemo 对对象属性包一层。

useMemo 有两个参数:

第一个参数是个函数,返回的对象指向同一个引用,不会创建新对象;
第二个参数是个数组,只有数组中的变量改变时,第一个参数的函数才会返回一个新的对象

子组件:

const ChildComp = memo(function ({name,onClick}) {
    console.log('render child-comp...');
    return <>
        <div>Child Comp ...{name.name}</div>
        <button onclick="{()=">onClick('hello')}>&#x6539;&#x53D8;name&#x503C;</button>
    </>
})

父组件:

function DifMemoCallBack() {
    const [count, setCount] = useState(0);
    const increment = ()=> setCount(count+1);

    const [name, setName] = useState('hi~');
    const [age, setAge] = useState(20);

    const changeName = useCallback((newName) => setName(newName),[]);

    //&#x5F53;&#x7236;&#x7EC4;&#x4EF6;&#x5411;&#x5B50;&#x7EC4;&#x4EF6;&#x4F20;&#x7684;&#x662F;&#x590D;&#x6742;&#x6570;&#x636E;&#x7C7B;&#x578B;&#x65F6;&#xFF0C;&#x5B50;&#x7EC4;&#x4EF6;&#x4E5F;&#x4F1A;&#x91CD;&#x65B0;&#x6E32;&#x67D3;
    //&#x7236;&#x7EC4;&#x4EF6;&#x6E32;&#x67D3;&#xFF0C;const info = { name, age } &#x4E00;&#x884C;&#x4F1A;&#x91CD;&#x65B0;&#x751F;&#x6210;&#x4E00;&#x4E2A;&#x65B0;&#x5BF9;&#x8C61;&#xFF0C;&#x5BFC;&#x81F4;&#x4F20;&#x9012;&#x7ED9;&#x5B50;&#x7EC4;&#x4EF6;&#x7684;info&#x5C5E;&#x6027;&#x503C;&#x53D8;&#x5316;&#xFF0C;&#x8FDB;&#x800C;&#x5BFC;&#x81F4;&#x5B50;&#x7EC4;&#x4EF6;&#x91CD;&#x65B0;&#x6E32;&#x67D3;
    // const info = {name,age};  //&#x590D;&#x6742;&#x6570;&#x636E;&#x7C7B;&#x578B;
    const info = useMemo(() => ({name,age}),[name,age]);

    return (
        <div>
            <button onclick="{increment}">&#x70B9;&#x51FB;&#x6B21;&#x6570;&#xFF1A;{count}</button>
            <childcomp name="{info}" onclick="{changeName}/">
        </childcomp></div>
    )
}
export default DifMemoCallBack;

再次点击父组件按钮,控制台中不再打印子组件被渲染的信息了。

总结:

function DifMemoCallBack() {
    const [count, setCount] = useState(0);
    const increment = ()=> setCount(count+1);

    const [name, setName] = useState('hi~');
    const [age, setAge] = useState(20);

    //&#x5F53;&#x7236;&#x7EC4;&#x4EF6;&#x53EA;&#x8C03;&#x7528;&#x4E86;&#x5B50;&#x7EC4;&#x4EF6;&#xFF0C;&#x6CA1;&#x6709;&#x4F20;&#x503C;&#x65F6;&#xFF0C;&#x5728;&#x5B50;&#x7EC4;&#x4EF6;&#x4E2D;&#x4F7F;&#x7528;memo&#x53EF;&#x89E3;&#x51B3;&#x91CD;&#x65B0;&#x6E32;&#x67D3;&#x7684;&#x95EE;&#x9898;
    // const changeName = (newName) => setName(newName); //&#x7236;&#x7EC4;&#x4EF6;&#x6E32;&#x67D3;&#x65F6;&#x4F1A;&#x521B;&#x5EFA;&#x4E00;&#x4E2A;&#x65B0;&#x7684;&#x51FD;&#x6570;

    //&#x5F53;&#x7236;&#x7EC4;&#x4EF6;&#x5411;&#x5B50;&#x7EC4;&#x4EF6;&#x4E2D;&#x4F20;&#x503C;&#xFF0C;&#x4E14;&#x4F20;&#x7684;&#x662F;&#x5B57;&#x7B26;&#x4E32;&#x65F6;&#xFF0C;&#x4F20;&#x8FC7;&#x53BB;&#x7684;changeName&#x91CD;&#x65B0;&#x521B;&#x5EFA;&#x4E86;&#xFF0C;&#x5BFC;&#x81F4;&#x5B50;&#x7EC4;&#x4EF6;&#x6E32;&#x67D3;&#xFF0C;&#x4F7F;&#x7528;useCallback&#x5B8C;&#x5584;&#x8FD9;&#x4E00;&#x7F3A;&#x9677;
    const changeName = useCallback((newName) => setName(newName),[]);

    //&#x5F53;&#x7236;&#x7EC4;&#x4EF6;&#x5411;&#x5B50;&#x7EC4;&#x4EF6;&#x4F20;&#x7684;&#x662F;&#x590D;&#x6742;&#x6570;&#x636E;&#x7C7B;&#x578B;&#x65F6;&#xFF0C;&#x5B50;&#x7EC4;&#x4EF6;&#x4E5F;&#x4F1A;&#x91CD;&#x65B0;&#x6E32;&#x67D3;
    //&#x7236;&#x7EC4;&#x4EF6;&#x6E32;&#x67D3;&#xFF0C;const info = { name, age } &#x4E00;&#x884C;&#x4F1A;&#x91CD;&#x65B0;&#x751F;&#x6210;&#x4E00;&#x4E2A;&#x65B0;&#x5BF9;&#x8C61;&#xFF0C;&#x5BFC;&#x81F4;&#x4F20;&#x9012;&#x7ED9;&#x5B50;&#x7EC4;&#x4EF6;&#x7684;info&#x5C5E;&#x6027;&#x503C;&#x53D8;&#x5316;&#xFF0C;&#x8FDB;&#x800C;&#x5BFC;&#x81F4;&#x5B50;&#x7EC4;&#x4EF6;&#x91CD;&#x65B0;&#x6E32;&#x67D3;
    // const info = {name,age};  //&#x590D;&#x6742;&#x6570;&#x636E;&#x7C7B;&#x578B;
    const info = useMemo(() => ({name,age}),[name,age]);

    return (
        <div>
            <button onclick="{increment}">&#x70B9;&#x51FB;&#x6B21;&#x6570;&#xFF1A;{count}</button>
            {/*<childcomp name="{name}" onclick="{changeName}/">*/}

            <childcomp name="{info}" onclick="{changeName}/">
        </childcomp></childcomp></div>
    )
}
export default DifMemoCallBack;

Original: https://blog.csdn.net/liuye066/article/details/127809647
Author: liuye066
Title: React中memo()、useCallback()、useMemo() 区别使用详解

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/656391/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球