IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> React 中 memo()、useCallback()、useMemo() -> 正文阅读

[JavaScript知识库]React 中 memo()、useCallback()、useMemo()

一、前言

渲染(Render)

React 基于虚拟 DOM 和高效 Diff 算法的完美配合,实现了对 DOM 最小粒度的更新。

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.memo 仅检查 props 变更。如果函数组件被 React.memo 包裹,且其实现中拥有 useState,useReduceruseContext 的 Hook,当 state 或 context 发生变化时,它仍会重新渲染。

三、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]);

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

如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。

// 父组件
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>
  );
}

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

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-04-14 23:43:36  更:2022-04-14 23:48:06 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 2:42:49-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码