沐游虞笔记
  • 前端面试题

    • HTML面试题汇总(无答案)
    • HTML面试题汇总
    • CSS 面试题汇总(无答案)
    • CSS 面试题汇总
    • javascript 面试题汇总(无答案)
    • javascript 面试题汇总
    • promise 面试题(无答案)
    • promise 面试题
    • 浏览器面试题汇总(无答案)
    • 浏览器面试题汇总
    • 网络面试题汇总(无答案)
    • 网络面试题汇总
    • 工程化面试题汇总(无答案)
    • 工程化面试题汇总
    • VUE面试题汇总(无答案)
    • VUE面试题汇总
  • 直播课文件

    • 静态页面学习指导
    • 属性的计算过程
    • 层叠继承规则总结
    • BFC
    • JS基础知识回顾
    • DOM 事件的传播机制
    • DOM 事件的注册和移除
    • 阻止事件默认行为
    • 基础领航考试题
    • 基础领航考试题(答案)
    • 2024前端发展
    • JS核心概念学习指导
    • 第三方库与工程化学习指导
    • Vue入门学习指导
    • vue进阶学习指导
    • 前端性能优化
  • 笔面试环节知识讲解

    • 目录
    • 图像处理
    • 图像处理(面试)
    • Webpack构建优化
    • Webpack构建优化(面试)
    • TTS性能优化
    • TTS性能优化(面试)
    • 实时协作
    • 实时协作(面试)
    • 网页复制图片到剪贴板
    • 网页复制图片到剪贴板(面试)
    • vite插件
    • vite插件(面试)
    • 表单数据同步与保持
    • 表单数据同步与保持(面试)
    • 优化虚拟列表
    • 优化虚拟列表(面试)
    • 微前端解决巨石应用
    • 微前端解决巨石应用(面试)
    • DNS解析与优化
    • DNS解析与优化(面试)
    • 前端监控
    • 前端监控(面试)
    • 12.跨标签页通信
    • 12.跨标签页通信(面试)
    • 13.Vite相关优化
    • 13.Vite相关优化(面试)
    • 14.计时器节流问题
    • 14.计时器节流问题(面试)
    • 15.多文件预览支持
    • 15.多文件预览支持(面试)
    • 16.defer优化白屏时间
    • 16.defer优化白屏时间(面试)
  • Vue3整体变化
  • Vue2响应式回顾
  • Vue3响应式变化
  • nextTick实现原理
  • 两道代码题
  • Vue运行机制
  • 渲染器核心功能
  • 事件绑定与更新
  • computed面试题
  • watch面试题
  • 图解双端diff
  • 图解快速dff
  • 最长递增子序列
  • 模板编译器
  • 模板编译提升
  • 组件name作用
  • 路由传参方式
  • 基础篇

    • 序章React介绍
    • JSX基础语法
    • React基本介绍
    • 表单
    • 生命周期
    • 组件与事件绑定
    • 组件状态与数据传递
    • Hooks
    • React--redux介绍
    • React-router介绍
  • 就业篇

    • 属性默认值和类型验证
    • 高阶组件
    • Ref
    • Context
    • Render Props
    • Portals
    • 错误边界
    • 组件渲染性能优化
    • 前端框架的理解
    • Reacti和Vue描述页面的区别
    • 前端框架的分类
    • 虚拟DoM
    • React整体架构
    • React渲染流程
    • Fiber双缓冲
    • MessageChannel
    • Scheduleri调度普通任务
    • Scheduleri调度延时任务
    • 最小堆
    • React中的位运算
    • beginWork工作流程
    • completeWork工作流程
    • 图解diff算法
    • commit工作流程
    • lane模型
    • React中的事件
    • Hooks原理
    • useStateuseReducer.
    • effect相关hook
    • useCallbackuseMemo
    • useRef
    • Update
    • 性能优化策略之eagerState
    • 性能优化策略之bailout
    • bailoutContextAPl
    • 性能优化对日常开发启示
  • 前端监控概述
  • 错误监控
  • 数据上报
  • 页面性能监控
  • 用户行为收集与埋点
  • CSS3手册
  • HTML5手册
  • JavaScript语言提升

    • es补充
    • 事件循环
    • promise基础
    • Promise的链式调用
    • Promise的静态方法
    • async和await
    • Promise相关面试题
  • 网络

    • 客户端与服务器
    • 关于 Apifox 的使用
  • git文档
  • 工程化

    • CommonJS
    • ES module
    • npm文档(包管理)
    • Lass笔记
    • webpack工具
  • canvas详解
  • uinapp笔记
  • 自动化测试
  • oauth2令牌

    • 认识Oauth2
    • 三方应用实现github授权
    • 微信三方应用登录实现
    • 支付宝沙箱支付功能
  • 前端面试题

    • HTML面试题汇总(无答案)
    • HTML面试题汇总
    • CSS 面试题汇总(无答案)
    • CSS 面试题汇总
    • javascript 面试题汇总(无答案)
    • javascript 面试题汇总
    • promise 面试题(无答案)
    • promise 面试题
    • 浏览器面试题汇总(无答案)
    • 浏览器面试题汇总
    • 网络面试题汇总(无答案)
    • 网络面试题汇总
    • 工程化面试题汇总(无答案)
    • 工程化面试题汇总
    • VUE面试题汇总(无答案)
    • VUE面试题汇总
  • 直播课文件

    • 静态页面学习指导
    • 属性的计算过程
    • 层叠继承规则总结
    • BFC
    • JS基础知识回顾
    • DOM 事件的传播机制
    • DOM 事件的注册和移除
    • 阻止事件默认行为
    • 基础领航考试题
    • 基础领航考试题(答案)
    • 2024前端发展
    • JS核心概念学习指导
    • 第三方库与工程化学习指导
    • Vue入门学习指导
    • vue进阶学习指导
    • 前端性能优化
  • 笔面试环节知识讲解

    • 目录
    • 图像处理
    • 图像处理(面试)
    • Webpack构建优化
    • Webpack构建优化(面试)
    • TTS性能优化
    • TTS性能优化(面试)
    • 实时协作
    • 实时协作(面试)
    • 网页复制图片到剪贴板
    • 网页复制图片到剪贴板(面试)
    • vite插件
    • vite插件(面试)
    • 表单数据同步与保持
    • 表单数据同步与保持(面试)
    • 优化虚拟列表
    • 优化虚拟列表(面试)
    • 微前端解决巨石应用
    • 微前端解决巨石应用(面试)
    • DNS解析与优化
    • DNS解析与优化(面试)
    • 前端监控
    • 前端监控(面试)
    • 12.跨标签页通信
    • 12.跨标签页通信(面试)
    • 13.Vite相关优化
    • 13.Vite相关优化(面试)
    • 14.计时器节流问题
    • 14.计时器节流问题(面试)
    • 15.多文件预览支持
    • 15.多文件预览支持(面试)
    • 16.defer优化白屏时间
    • 16.defer优化白屏时间(面试)
  • Vue3整体变化
  • Vue2响应式回顾
  • Vue3响应式变化
  • nextTick实现原理
  • 两道代码题
  • Vue运行机制
  • 渲染器核心功能
  • 事件绑定与更新
  • computed面试题
  • watch面试题
  • 图解双端diff
  • 图解快速dff
  • 最长递增子序列
  • 模板编译器
  • 模板编译提升
  • 组件name作用
  • 路由传参方式
  • 基础篇

    • 序章React介绍
    • JSX基础语法
    • React基本介绍
    • 表单
    • 生命周期
    • 组件与事件绑定
    • 组件状态与数据传递
    • Hooks
    • React--redux介绍
    • React-router介绍
  • 就业篇

    • 属性默认值和类型验证
    • 高阶组件
    • Ref
    • Context
    • Render Props
    • Portals
    • 错误边界
    • 组件渲染性能优化
    • 前端框架的理解
    • Reacti和Vue描述页面的区别
    • 前端框架的分类
    • 虚拟DoM
    • React整体架构
    • React渲染流程
    • Fiber双缓冲
    • MessageChannel
    • Scheduleri调度普通任务
    • Scheduleri调度延时任务
    • 最小堆
    • React中的位运算
    • beginWork工作流程
    • completeWork工作流程
    • 图解diff算法
    • commit工作流程
    • lane模型
    • React中的事件
    • Hooks原理
    • useStateuseReducer.
    • effect相关hook
    • useCallbackuseMemo
    • useRef
    • Update
    • 性能优化策略之eagerState
    • 性能优化策略之bailout
    • bailoutContextAPl
    • 性能优化对日常开发启示
  • 前端监控概述
  • 错误监控
  • 数据上报
  • 页面性能监控
  • 用户行为收集与埋点
  • CSS3手册
  • HTML5手册
  • JavaScript语言提升

    • es补充
    • 事件循环
    • promise基础
    • Promise的链式调用
    • Promise的静态方法
    • async和await
    • Promise相关面试题
  • 网络

    • 客户端与服务器
    • 关于 Apifox 的使用
  • git文档
  • 工程化

    • CommonJS
    • ES module
    • npm文档(包管理)
    • Lass笔记
    • webpack工具
  • canvas详解
  • uinapp笔记
  • 自动化测试
  • oauth2令牌

    • 认识Oauth2
    • 三方应用实现github授权
    • 微信三方应用登录实现
    • 支付宝沙箱支付功能
  • 基础篇

    • 序章:React课程介绍
    • JSX 基础语法
    • React 基本介绍
    • 表单
    • 生命周期
    • 组件与事件绑定
    • 组件状态与数据传递
    • Hooks
    • React-redux 介绍
    • React-router介绍
  • 就业篇

    • 属性默认值和类型验证
    • 高阶组件
    • Ref
    • Context
    • Render Props
    • Portals
    • 错误边界
    • 组件渲染性能优化
      • shouldComponentUpdate 与 PureComnent
      • React.memo
      • useCallback
      • useMemo
    • 前端框架的理解
    • React和Vue描述页面的区别
    • 前端框架的分类
    • 虚拟 DOM
    • React 整体架构
    • React 渲染流程
    • Fiber双缓冲
    • MessageChannel
    • Scheduler调度普通任务
    • Scheduler调度延时任务
    • 最小堆
    • React中的位运算
    • beginWork工作流程
    • completeWork工作流程
    • 图解diff算法
    • commit 工作流程
    • lane模型
    • React 中的事件
    • Hooks原理
    • useState和useReducer
    • effect相关hook
    • useCallback和useMemo
    • useRef
    • Update
    • 性能优化策略之eagerState
    • 性能优化策略之bailout
    • bailout和ContextAPI
    • 性能优化对日常开发启示
  • react
  • 就业篇
luzhichang
2024-10-17
目录

组件渲染性能优化

# 8. 组件渲染性能优化

在本小节,我们将会探讨组件在渲染时,如何优化渲染性能问题。

涉及到的内容会包含 shouldComponentUpdate、PureComnent、React.memo、useMemo、useCallback 等。

# shouldComponentUpdate 与 PureComnent

shouldComponentUpdate 与 PureComnent 都与类组件相关,所以下面会以类组件来示例。

import React, { Component } from 'react'

export default class App extends Component {

  constructor() {
    super();
    this.state = {
      counter: 1
    }
  }
  render() {
    console.log("App 渲染了");
    return (
      <div>
        <h1>App 组件</h1>
        <div>{this.state.counter}</div>
        <button onClick={() => this.setState({
          counter : 1
        })}>+1</button>
      </div>
    )
  }
}

在上面的代码中,我们书写了一个简单的计数器,按钮在点击的时候仍然是设置 counter 的值为 1,不过,虽然 counter 的值没有变,整个组件仍然是重新渲染了的。显然,这一次渲染是没有必要的。

此时,我们就可以使用 shouldComponentUpdate 来进行优化。

文档地址:https://zh-hans.reactjs.org/docs/react-component.html#shouldcomponentupdate

当 props 或 state 发生变化时,shouldComponentUpdate 会在渲染执行之前被调用。返回值默认为 true。首次渲染或使用 forceUpdate 方法时不会调用该方法。

下面我们来使用 shouldComponentUpdate 优化上面的示例:

import React, { Component } from 'react'
import { objectEqual } from "./utils/tools"

export default class App extends Component {

  constructor() {
    super();
    this.state = {
      counter: 1
    }
  }

  /**
   * 
   * @param {*} nextProps 新的 props
   * @param {*} nextState 新的 state
   * @returns 
   */
  shouldComponentUpdate(nextProps, nextState) {
    // shouldComponentUpdate会根据返回值来决定是否重新渲染
    // 默认是 true,要重新渲染
    // 如果返回 false,则不会重新渲染
    // 我们就需要将当前的 props 和 state 与新的 props 和 state 进行一个比较
    if (objectEqual(this.props, nextProps) && objectEqual(this.state, nextState)) {
      // 如果新旧 props 和 state 都是相同的,那么就返回 false,不需要重新渲染
      return false;
    }
    return true;
  }

  render() {
    console.log("App 渲染了");
    return (
      <div>
        <h1>App 组件</h1>
        <div>{this.state.counter}</div>
        <button onClick={() => this.setState({
          counter: Math.floor(Math.random() * 3 + 1)
        })}>+1</button>
      </div>
    )
  }
}

在上面的代码中,我们在类组件中书写了一个 shouldComponentUpdate 的生命周期钩子函数,该函数会在渲染执行之前被调用,函数内部能够接收到新的属性和新的状态,我们要做的就是让新的属性和状态和当前的属性以及状态进行浅比较,如果相同则返回 false,也就是不重新渲染。

此方法仅作为性能优化的方式而存在,不要企图依靠此方法来“阻止”渲染。另外,现在 React 官方已经提供了 PureComponent,因此一般情况下我们是不需要手写 shouldComponentUpdate 的。

PureComponent 文档:https://zh-hans.reactjs.org/docs/react-api.html#reactpurecomponent

React.PureComponent 与 React.Component 很相似。两者的区别在于 React.Component 并未实现 shouldComponentUpdate( ),而 React.PureComponent 中以浅层对比 prop 和 state 的方式来实现了该函数。

例如我们将上面的示例直接修改为 PureComponent,如下:

import React, { PureComponent } from 'react'
export default class App extends PureComponent {

  constructor() {
    super();
    this.state = {
      counter: 1
    }
  }

  render() {
    console.log("App 渲染了");
    return (
      <div>
        <h1>App 组件</h1>
        <div>{this.state.counter}</div>
        <button onClick={() => this.setState({
          counter: Math.floor(Math.random() * 3 + 1)
        })}>+1</button>
      </div>
    )
  }
}

可以看到,效果相同,但是代码精简了很多。在使用 PureComponent 的时候,有一点一定要注意,这也是官方所强调,就是内部做的是浅比较,这意味下面的代码是无法更新的:

import React, { PureComponent } from 'react'
export default class App extends PureComponent {

  constructor() {
    super();
    this.state = {
      stu: ["张三", "李四"]
    }
  }

  addStuHandle = () => {
    this.state.stu.push("王武");
    this.setState({
      stu: this.state.stu
    })
  }

  render() {
    const stuLis = this.state.stu.map((it, index) => (<li key={index}>{it}</li>))
    return (
      <div>
        <h1>App 组件</h1>
        <ul>
          {stuLis}
        </ul>
        <button onClick={this.addStuHandle}>添加学生</button>
      </div>
    )
  }
}

究其原因,是因为数组的地址并没有发生更改,而是数组内部发生的更改,但是 PureComponent 是浅比较,会认为数组并没有发生更改,因此不会进行渲染更新。(如果使用 Component 则是没有问题的,因为 React.Component 并未实现 shouldComponentUpdate)

但是不管怎样,我们都应该返回一个新的数组,而不是把原来的数组赋值给 stu:

addStuHandle = () => {
    const arr = [...this.state.stu]
    arr.push("王武");
    this.setState({
      stu: arr
    })
}

# React.memo

shouldComponentUpdate 与 PureComnent 主要是优化类组件的渲染性能,那么如果是函数组件该怎么办呢?

在 React 中,为我们提供了 React.memo。

文档地址:https://zh-hans.reactjs.org/docs/react-api.html#reactmemo

这是一个高阶组件,如果你的组件在相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。

来看一个例子:

import React, { useState } from 'react';
import ChildCom1 from "./components/ChildCom1"

function App() {

  const [counter1, setCounter1] = useState(1);
  const [counter2, setCounter2] = useState(1);

  console.log("App 渲染了");
  return (
    <div>
      <div>{counter1}</div>
      <button onClick={() => setCounter1(counter1 + 1)}>+1</button>
      <ChildCom1 counter={counter2} setCounter={setCounter2} />
    </div>
  );
}

export default App;
import React from "react"
function ChildCom1(props) {
    console.log("ChildCom1 渲染了")
    return (
        <div>
            <div>ChildCom1</div>
            <div>{props.counter}</div>
        </div>
    );
}

export default ChildCom1;

在上面的示例中,App 根组件中定义了两个状态 counter1 和 counter2,在 App 组件内部修改 counter1,然后 counter2 作为 porps 传递给子组件,此时我们修改 counter1 的状态时,子组件也会跟着更新,原因很简单,因为父组件更新了,那你子组件当然要重新更新。

但是,我们发现,其实**子组件的更新是没有必要的,因为传递给子组件的 props 并没有发生变化**,此时我们就可以使用 React.memo,如下:

import React from "react"
function ChildCom1(props) {
    console.log("ChildCom1 渲染了")
    return (
        <div>
            <div>ChildCom1</div>
            <div>{props.counter}</div>
        </div>
    );
}

export default React.memo(ChildCom1);

之后我们再更新 counter1 时,由于传递给子组件的 counter2 这个 props 属性并没有变化,因此子组件不会更新。

注意:默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。

举个例子:

import React, { useState } from 'react';
import ChildCom1 from "./components/ChildCom1"

function App() {

  const [counter1, setCounter1] = useState(2);
  const [stu, setStu] = useState(["张三","李四"]);

  console.log("App 渲染了");

  function clickHandle(){
    setCounter1(counter1 + 1);
    stu.push("王武");
    setStu(stu)
  }

  return (
    <div>
      <div>{counter1}</div>
      <button onClick={clickHandle}>+1</button>
      <ChildCom1 stu={stu} setStu={setStu} />
    </div>
  );
}

export default App;
import React from "react"
function ChildCom1(props) {
    console.log("ChildCom1 渲染了")
    const lis = props.stu.map((it,index)=>(<li key={index}>{it}</li>))
    return (
        <div>
            <div>ChildCom1</div>
            <ul>{lis}</ul>
        </div>
    );
}

export default React.memo(ChildCom1);

这个例子和我们之前的 PureComponent 所举的例子很相似,由于是在原来的数组上面进行的修改,数组的地址并没有发生变化,因此 React.memo 返回的组件并不会更新。

实际上 React.memo 的源码就是返回一个 PureComponent 组件:

function memo(FuncComp){
	return class Memo extends PureComponent{
     render(){
          return <>{FuncComp(this.props)}</>
       }
   }
}

此时要解决这个问题也很简单,和前面一样,直接返回一个新的数组,例如:

function clickHandle(){
    setCounter1(counter1 + 1);
    const arr = [...stu];
    arr.push("王武");
    setStu(arr)
}

另外,在使用 React.memo 的时候还支持传入第二个自定义的比较函数参数,例如:

function MyComponent(props) {
  /* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
  /*
  如果把 nextProps 传入 render 方法的返回结果与
  将 prevProps 传入 render 方法的返回结果一致则返回 true,
  否则返回 false
  */
}
export default React.memo(MyComponent, areEqual);

# useCallback

正常情况下,如果组件各自内部维护自己的数据,那么组件更新的时候相互并不会影响,例如:

App 根组件对应样式:

.container {
    width: 500px;
    height: 200px;
    border: 1px solid;
    margin: 0 auto;
}

.btnContainer {
    text-align: center;
}

.childComContainer {
    display: flex;
    justify-content: space-between;
}

App 根组件,引入了 ChildCom1 和 ChildCom2 这两个子组件:

import { useState } from 'react';
import ChildCom1 from "./components/ChildCom1"
import ChildCom2 from "./components/ChildCom2"

import styles from "./css/App.module.css"

function App() {
  const [counter, setCounter] = useState(0);
  console.log("App渲染了")
  return (
    <div className={styles.container}>
      <div className={styles.btnContainer}>
        <div>{counter}</div>
        <button onClick={() => setCounter(counter + 1)}>+1</button>
      </div>


      <div className={styles.childComContainer}>
        <ChildCom1 />
        <ChildCom2 />
      </div>

    </div>
  );
}

export default App;

ChildCom1 子组件:

import { useState } from "react"
function ChildCom1() {
    const [counter, setCounter] = useState(0);
    console.log("ChildCom1 渲染了")
    return (
        <div style={{
            width: "200px",
            height: "100px",
            border: "1px solid"
        }}>
            ChildCom1
            <div>{counter}</div>
            <button onClick={() => setCounter(counter + 1)}>+1</button>
        </div>
    );
}

export default ChildCom1;

ChildCom2 组件基本相同

此时在我们的应用中,各个组件内部维护了自身的数据,组件内部数据的更新并不会影响到同级组件和祖级组件。效果如下:

iShot_2022-12-02_15.46.37

可以看到,父组件的更新会导致两个子组件更新,这是正常的,子组件各自的更新不会影响其他组件。

但是,倘若两个子组件的数据都来自于父组件,情况就不一样了。

这里我们把上面的代码稍作修改,如下:

App.jsx 根组件,为两个子组件提供了数据以及修改数据的方法

import { useState } from 'react';
import ChildCom1 from "./components/ChildCom1"
import ChildCom2 from "./components/ChildCom2"

import styles from "./css/App.module.css"

function App() {
  const [counter, setCounter] = useState(0);
  const [counter1, setCounter1] = useState(0);
  const [counter2, setCounter2] = useState(0);
  console.log("App渲染了")
  return (
    <div className={styles.container}>
      <div className={styles.btnContainer}>
        <div>{counter}</div>
        <button onClick={() => setCounter(counter + 1)}>+1</button>
      </div>


      <div className={styles.childComContainer}>
        <ChildCom1 counter={counter1} setCounter={setCounter1}/>
        <ChildCom2 counter={counter2} setCounter={setCounter2}/>
      </div>

    </div>
  );
}

export default App;

ChildCom1 子组件接收从父组件传递过来的数据,并调用父组件传递过来的方法修改数据

function ChildCom1(props) {
    console.log("ChildCom1 渲染了")
    return (
        <div style={{
            width: "200px",
            height: "100px",
            border: "1px solid"
        }}>
            ChildCom1
            <div>{props.counter}</div>
            <button onClick={() => props.setCounter(props.counter + 1)}>+1</button>
        </div>
    );
}

export default ChildCom1;

效果如下:

iShot_2022-12-02_15.52.31

可以看到,在更新子组件的数据时,由于数据是从父组件传递下去的,相当于更新了父组件数据,那么父组件就会重新渲染,最终导致的结果就是父组件下面所有的子组件都重新渲染了。

首先,我们会想到使用前面所讲的 React.memo 来解决这个问题,如下:

import React from "react"
function ChildCom1(props) {
    console.log("ChildCom1 渲染了")
    return (
        <div style={{
            width: "200px",
            height: "100px",
            border: "1px solid"
        }}>
            ChildCom1
            <div>{props.counter}</div>
            <button onClick={() => props.setCounter(props.counter + 1)}>+1</button>
        </div>
    );
}

// 相同的 props 传入的时候该组件不需要重新渲染
export default React.memo(ChildCom1);

在上面的代码中,我们使用 React.memo 来缓存 ChildCom1 组件,这样在相同的 props 传入时,该组件不会重新渲染。

但是假设此时 App 组件还有一个单独的函数传入,那就不那么好使了:

function App() {
  // App 组件自身有一个状态
  // ...
  console.log("App 渲染了")

  function test() {
    console.log("test");
  }


  return (
    <div className={styles.container}>
      {/* ... */}

      <div className={styles.childComContainer}>
        <ChildCom1 counter={counter1} setCounter={setCounter1} test={test} />
        <ChildCom2 counter={counter2} setCounter={setCounter2} test={test} />
      </div>
    </div>
  );
}

在上面的代码中,我们还向两个子组件传入了一个 test 函数,由于每次 App 组件的重新渲染会生成新的 test 函数,所以对于两个子组件来讲传入的 test 导致 props 不同所以都会重新渲染。

此时就可以使用 useCallback 来解决这个问题,语法如下:

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。

接下来我们来使用 useCallback 优化上面的问题,对 App.jsx 做如下的修改:

import React, { useState, useCallback } from 'react';
import ChildCom1 from "./components/ChildCom1"
import ChildCom2 from "./components/ChildCom2"

import styles from "./css/App.module.css"

function App() {

  const [counter, setCounter] = useState(1); // 这是 App 组件自身维护的状态
  const [counter1, setCounter1] = useState(1); // 这是要传递给 ChildCom1 组件的数据
  const [counter2, setCounter2] = useState(1); // 这是要传递给 ChildCom2 组件的数据

  console.log("App组件渲染了")

  // 每次重新渲染的时候,就会生成一个全新的 test 函数
  // 使用了 useCallback 之后,我们针对 test 函数做了一个缓存  
  const newTest = useCallback(function test(){
    console.log("test触发了")
  },[])

  return (
    <div className={styles.container}>
      {/* 自身的计数器 */}
      <div className={styles.btnContainer}>
        <div>counter:{counter}</div>
        <button onClick={() => setCounter(counter + 1)}>+1</button>
      </div>

      {/* 使用子组件 */}
      <div className={styles.childComContainer}>
        <ChildCom1 counter={counter1} setCounter={setCounter1} test={newTest}/>
        <ChildCom2 counter={counter2} setCounter={setCounter2} test={newTest}/>
      </div>
    </div>
  );
}

export default App;

在上面的代码中,我们对 test 函数做了缓存,从而保证每次传入到子组件的这个 props 和之前是相同的。

记住:useCallback 主要就是对函数进行缓存

# useMemo

最后要介绍的是 useMemo,其语法如下:

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

主要用于返回一个 memoized 值。

文档地址:https://zh-hans.reactjs.org/docs/hooks-reference.html#usememo

某些时候,组件中某些值需要根据状态进行一个二次计算(类似于 Vue 中的计算属性),由于函数组件一旦重新渲染,就会重新执行整个函数,这就导致之前的二次计算也会重新执行一次,例如:

import React, { useState } from 'react';

function App() {

  const [count, setCount] = useState(1);
  const [val, setValue] = useState('');

  console.log("App 渲染了");

  function getNum() {
    console.log('调用了!!!');
    return count + 100;
  }


  return (
    <div>
      <h4>总和:{getNum()}</h4>
      <div>
        <button onClick={() => setCount(count + 1)}>+1</button>
        {/* 文本框的输入会导致整个组件重新渲染 */}
        <input value={val} onChange={event => setValue(event.target.value)} />
      </div>
    </div>
  );
}

export default App;

在上面的示例中,文本框的输入会导致整个 App 组件重新渲染,但是 count 的值是没有改变的,所以 getNum 这个函数也是没有必要重新执行的。

此时,我们就可以使用 useMemo 来缓存这个计算值,除非 count 变了,否则直接使用缓存值,不需要重新做二次计算(调用 getNum)。

如下:

import React, { useState,useMemo } from 'react';

function App() {

  const [count, setCount] = useState(1);
  const [val, setValue] = useState('');

  console.log("App 渲染了");


  const getNum = useMemo(() => {
    console.log('调用了!!!!!');
    return count + 100;
  }, [count])


  return (
    <div>
      <h4>总和:{getNum}</h4>
      <div>
        <button onClick={() => setCount(count + 1)}>+1</button>
        {/* 文本框的输入会导致整个组件重新渲染 */}
        <input value={val} onChange={event => setValue(event.target.value)} />
      </div>
    </div>
  );
}

export default App;

在上面的示例中,我们使用了 useMemo 来缓存二次计算的值,并设置了依赖项 count,只有在 count 发生改变时,才会重新执行二次计算。

面试题:useMemo 和 useCallback 的区别及使用场景?

useMemo 和 useCallback 接收的参数都是一样,第一个参数为回调,第二个参数为要依赖的数据。

共同作用: 仅仅依赖数据发生变化,才会去更新缓存。

两者区别:

  1. useMemo 计算结果是 return 回来的值, 主要用于缓存计算结果的值。应用场景如:需要进行二次计算的状态
  2. useCallback 计算结果是函数, 主要用于缓存函数,应用场景如: 需要缓存的函数,因为函数式组件每次任何一个 state 的变化,整个组件都会被重新刷新,一些函数是没有必要被重新刷新的,此时就应该缓存起来,提高性能,和减少资源浪费。

-EOF-

错误边界
前端框架的理解

← 错误边界 前端框架的理解→

Theme by Vdoing | Copyright © 2021-2024 蜀ICP备2024068710号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式