0
点赞
收藏
分享

微信扫一扫

React 中 memo()、useCallback()、useMemo() 理解

全栈学习笔记 2022-04-14 阅读 182

一、前言

渲染(Render)

react 处理 render 的基本思维模式是每次一有变动就会去重新渲染整个应用。会将 render 函数返回的虚拟 DOM 树与老的进行比较,从而确定 DOM 要不要更新、怎么更新。

何时触发渲染(Render)

  • 组件挂载
  • setState() 方法被调用 ( 当 setState 传入 null 的时候,并不会触发 render )

二、React.memo()

// 父组件
import React, { useState } from 'react';
import ChildComponent from './ChildComponent';

export default function IndexPage() {
  const [ name, setName ] = useState('父组件');
  const [count, setCount] = useState(0);

  return (
     <div>
      <button onClick={() => setCount(count + 1)}>
        count++: {count}
      </button>
      <br />
      <ChildComponent name={name} />
    </div>
  );
}
// 子组件
import React, { useState } from 'react';

const ChildComponent = ({ name }) => {
    console.log('Child');
    
    const [childCount, setChildCount] = useState(0);

    return (
       <div>
          <button onClick={() => setChildCount(childCount + 1)}>
             childCount++ :{childCount}
          </button>
       </div>
    )
}

export default ChildComponent;

子组件中有条 console.log('Child') 语句,每当子组件被渲染时,都会在控制台看到一条打印信息。
这时点击父组件中的 button,会修改 count 变量的值,触发父组件render,此时子组件没有任何变化(props、childCount),但在控制台中仍然看到子组件被渲染的打印信息。

在这里插入图片描述

问题:组件在相同 props 的情况下只渲染相同的结果一次,即便父组件渲染,也不要渲染子组件。

解决:子组件用 React.memo() 包裹,如果组件在相同 props 的情况下渲染相同的结果,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。

//	子组件
import React, { memo, useState } from 'react';

const ChildComponent = ({ name }) => {
    console.log('Child');
    const [childCount, setChildCount] = useState(0);

    return (
        <div>
            <button onClick={() => setChildCount(childCount + 1)}>
                childCount++ :{childCount}
            </button>
        </div>
    )
}
//	子组件用 React.memo() 包裹
export default memo(ChildComponent);

此时再次点击按钮,可以看到控制台没有打印子组件被渲染的信息了。
在这里插入图片描述

三、React.useCallback()

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

// 父组件
import React, { useState } from 'react';
import ChildComponent from './ChildComponent';

export default function IndexPage() {
  const [name, setName] = useState('父组件');
  const [count, setCount] = useState(0);

 // 父组件渲染时会创建一个新的函数
  const changeName = (el) => setName(el)
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        count++: {count}
      </button>
      <br />
      <p>{name}</p>
      <ChildComponent name={name} changeName={changeName} />
    </div>
  );
}
//	子组件
import React, { memo, useState } from 'react';

const ChildComponent = ({ name }) => {
     console.log('Child');
    const [childCount, setChildCount] = useState(0);

    return (
        <div>
            <button onClick={() => changeName('子组件')}>changeName</button>
            <button onClick={() => setChildCount(childCount + 1)}>
                childCount++ :{childCount}
            </button>
        </div>
    )
}
//	仍然用 React.memo() 包裹
export default memo(ChildComponent);

这时点击父组件中的 button,会修改 count 变量的值,触发父组件render,此时调用子组件时 传递了 name 属性和 onClick 属性,在控制台中会看到子组件被渲染的打印信息。
在这里插入图片描述
原因:父组件 render 时,会重新创建 changeName() 函数,此时子组件接收的 props 发生改变,所以子组件 React.memo() 检查 props 变更也会 render。

解决:父组件的 changeName() 方法,用 useCallback 钩子函数包裹一层,会把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。

// 父组件
import React, { useState } from 'react';
import ChildComponent from './ChildComponent';

export default function IndexPage() {
  const [name, setName] = useState('父组件');
  const [count, setCount] = useState(0);

 // 父组件渲染时会创建一个新的函数
  const changeName = useCallback((el) => setName(el), [])
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        count++: {count}
      </button>
      <br />
      <p>{name}</p>
      <ChildComponent name={name} changeName={changeName} />
    </div>
  );
}

在这里插入图片描述

三、React.useMemo()

上面例子中,父组件调用子组件时传递的 name 属性是个字符串,若将其换成传递对象:

// 父组件
import React, { useState } from 'react';
import ChildComponent from './ChildComponent';

export default function IndexPage() {
  const [name, setName] = useState('父组件');
  const [count, setCount] = useState(0);
  const obj = { name: '父组件', name2: '子组件' };

 // 父组件渲染时会创建一个新的函数
 const changeName = useCallback((el) => setName(el), [])
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        count++: {count}
      </button>
      <br />
      <p>{name}</p>
      <ChildComponent obj={obj} name={name} changeName={changeName} />
    </div>
  );
}
//	子组件
import React, { memo, useState } from 'react';

const ChildComponent = ({ name, obj }) => {
     console.log('Child');
    const [childCount, setChildCount] = useState(0);

    return (
        <div>
            <button onClick={() => changeName('子组件')}>changeName</button>
            <button onClick={() => setChildCount(childCount + 1)}>
                childCount++ :{childCount}
            </button>
        </div>
    )
}
//	仍然用 React.memo() 包裹
export default memo(ChildComponent);

在这里插入图片描述

影响:点击父组件按钮,父组件 render 时,const obj = { name: '父组件', name2: '子组件' } 一行会重新生成一个新对象,导致传递给子组件的 obj 属性值变化,进而导致子组件重新渲染。

解决:把“obj 对象属性作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。有助于避免在每次渲染时都进行高开销的计算。

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
// 父组件
import React, { useState } from 'react';
import ChildComponent from './ChildComponent';

export default function IndexPage() {
  const [name, setName] = useState('父组件');
  const [count, setCount] = useState(0);
  const obj = useMemo(() => ({ name: '父组件', name2: '子组件' }), [name,name2])

 // 父组件渲染时会创建一个新的函数
 const changeName = useCallback((el) => setName(el), [])
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        count++: {count}
      </button>
      <br />
      <p>{name}</p>
      <ChildComponent obj={obj} name={name} changeName={changeName} />
    </div>
  );
}

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

举报

相关推荐

0 条评论