react hooks--React.memo

ops/2024/9/23 7:43:57/

基本语法

React.memo 高阶组件的使用场景说明:

React 组件更新机制:只要父组件状态更新,子组件就会无条件的一起更新。

  • 子组件 props 变化时更新过程:组件代码执行 -> JSX Diff(配合虚拟 DOM)-> 渲染(变化后的内容)【 DOM 操作】。
  • 子组件 props 无变化更新过程:组件代码执行 -> JSX Diff(配合虚拟 DOM)【无 DOM 操作】。

注意:此处更新指的是组件代码执行、JSX 进行 Diff 操作(纯 JS 的操作,速度非常快,不会对性能产生太多影响)。

  • 如果组件 props 改变了,那么,该组件就必须要更新,才能接收到最新的 props。
  • 但是,如果组件 props 没有改变时,组件也要进行一次更新。实际上,这一次更新是没有必要的。

如果要避免组件 props 没有变化而进行的不必要更新(Diff),这种情况下,就要使用 React.memo 高阶组件。

注:对于 class 组件来说,可以使用 PureComponent 或 shouldComponentUpdate 钩子函数来实现

import { useState } from 'react'
import ReactDOM from 'react-dom'const Child2 = ({ count }) => {console.log('Child2 子组件代码执行了')return <div style={{ backgroundColor: '#abc' }}>子组件2:{count}</div>
}const Child1 = () => {console.log('Child1 子组件代码执行了')return <div style={{ backgroundColor: '#def' }}>子组件1</div>
}const App = () => {const [count, setCount] = useState(0)return (<div style={{ backgroundColor: 'pink', padding: 10 }}><h1>计数器:{count}</h1><button onClick={() => setCount(count + 1)}>+1</button><hr />{/* 子组件 */}<Child1 /><br /><Child2 count={count} /></div>)
}export default App

语法:

使用场景:当你想要避免函数组件 props 没有变化而产生的不必要更新时,就要用到 React.memo 了。

作用:记忆组件上一次的渲染结果,在 props 没有变化时复用该结果,避免函数组件不必要的更新

解释:

  • React.memo 是一个高阶组件,用来记忆(memorize)组件
  • 参数(Child):需要被记忆的组件,或者说是需要避免不必要更新的组件。
  • 返回值(MemoChild):React 记住的 Child 组件。

原理:通过对比检查更新前后 props 是否相同,来决定是否复用上一次的渲染结果

  • 如果相同,复用上一次的渲染结果;
  • 如果不同,重新渲染组件。

并不是所有的组件都适合使用memo,比如child2组件,每次都需要重新渲染,使用memo反而会使性能变得更低,逻辑也变得更复杂

基本用法

如果你的组件在相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果

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

默认情况下,React.memo 只会对更新前后的 props 进行浅对比(shallow compare)与 PureComponent 相同。

也就是说,对于对象类型的 prop 来说,只会比较引用

  • 如果更新前后的引用相同,复用上一次的渲染结果(不会重新渲染该组件)。
  • 如果更新前后的引用不同,重新渲染该组件。

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

如果你要手动控制比较过程,可以使用 React.memo 的第二个参数:

解释:

  • 第二个参数:用来比较更新前后 props 的函数。
  • 返回值:如果返回 true,表示记住(不重新渲染)该组件;如果返回 false,表示重新渲染该组件。

子组件Callback.jsx

export default function Callback() {console.log('callback')return (<div>callback</div>)
}

父组件App.jsx:

import Callback from './components/Callback'
import {useState} from "react";export default function App() {const [age, setAge] = useState(20);function changeAge() {setAge(age + 1)}return (<div>age: {age}<button onClick={changeAge}>修改age</button><Callback /></div>)
}

修改父组件的state值后,函数子组件内部的代码运行了,相对而言有点费性能

优化如下:

使用React.memo包裹子组件,缓存上一次的结果,默认情况下其只会对复杂对象做浅层对比,如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。

子组件Callback.jsx

import React from 'react';function Callback() {console.log('callback')return (<div>callback</div>)
}++ export default React.memo(Callback)

优化后,父组件的state数据变更后,没有执行打印结果。

在父组件中定义个fn函数传入到子组件中:

import Callback from './components/Callback'
import { useState} from "react";export default function App() {console.log('app')const [age, setAge] = useState(20);function changeAge() {setAge(age + 1)}function fn() {}return (<div>age: {age}<button onClick={changeAge}>修改age</button><Callback fn={fn} /></div>)
}

此时,父组件的state数据改变后,会引起子组件的内部代码运行。打印了结果,相对而言性能有损耗。

优化如下:

useCallback优化性能

fn函数通过useCallback缓存起来

import Callback from './components/Callback'
import {useCallback, useState} from "react";export default function App() {console.log('app')const [age, setAge] = useState(20);function changeAge() {setAge(age + 1)}let fn = useCallback(() => {}, [])return (<div>age: {age}<button onClick={changeAge}>修改age</button><Callback fn={fn} /></div>)
}

此时修改父组件的state数据没有引起子组件的内部代码运行

因为fn函数被缓存了,地址没有发生改变,React.memo就认为props数据没有发生改变,所以没有表要重新运行函数。

useCallback如何做的性能优化?

  • 在函数组件中,定义的一般的普通函数在每次组件更新渲染时都会重新生成一个函数,此时函数地址发生改变了,就会引起子组件内部代码运行,损耗性能。
  • 优化:使用useCallback将函数缓存,每次组件更新渲染时,这个函数地址没有发生改变,所以传入到子组件的props数据没有发生发生改变,所以子组件内部代码没有重新执行。
  • React.memo是数据的浅对比,也就是地址的对比,只要传入参数地址发生改变就会重新执行子组件的代码。

总结

useMemo 用于缓存一些耗时的计算结果(返回值),只有当依赖项改变时才重新进行计算。 useCallback(func, [deps]) 等同于 useMemo(() => func, [deps]) useCallback 缓存的是方法的引用,useMemo 缓存的是方法的返回值,适用场景都是避免不必要的子组件渲染。 在类组件中有 React.PureComponent,与之对应的函数组件可以使用 React.memo,它们都会在自身 re-render 时,对每一个 props 项进行浅对比,如果引用没有发生改变,就不会触发渲染。 那么,useMemo 和 React.memo 有什么共同点呢?前者可以在组件内部使用,可以拥有比后者更细粒度的依赖控制。它们两个与 useCallback 的本质一样,都是进行依赖控制。


http://www.ppmy.cn/ops/114660.html

相关文章

无人机之激光避障篇

无人机的激光避障技术是通过激光传感器来感知和避开周围障碍物的一种高级技术。以下是关于无人机激光避障技术的详细解析&#xff1a; 一、技术原理 激光避障技术利用激光束的直线传播和反射特性&#xff0c;通过发送激光束并接收反射回来的信号&#xff0c;来检测和计算周围障…

PHP isset() 和 empty() 区别

这两个函数在PHP编程中经常用于检查变量是否已设置或是否为空&#xff0c;但它们各自的用途和行为有所不同。 isset() 函数 isset()函数用于检测变量是否已设置并且非NULL。如果指定变量存在并且值不是NULL&#xff0c;则返回true&#xff1b;否则返回false。这个函数可以检查…

[2025]基于微信小程序慢性呼吸系统疾病的健康管理(源码+文档+解答)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

QT 信号和槽函数

信号和槽函数介绍 conncet(sender, signal, receiver, slot) /* * 1. 信号发出者&#xff1b; * 2. 信号&#xff1b; * 3. 信号接收者&#xff1b; * 4. 接受到信号执行任务&#xff1b; 槽函数 */自定义信号和槽函数 场景 &#xff1a;老师饿了&#xff0c;学生请客&#xf…

创建索引遇到这个Bug,19c中还没有修复

莫名其妙的错误 近日在创建索引时突然报出 ORA-01792 错误&#xff0c;根据错误提示&#xff0c;显示表上的列数量超过了1000&#xff0c;但是显然这个表上并没有这么多的列。 [oraclemyora19c ~]$ oerr ora 01792 01792, 00000, "maximum number of columns in a table…

Linux驱动开发 ——架构体系

只读存储器&#xff08;ROM&#xff09; 1.作用 这是一种非易失性存储器&#xff0c;用于永久存储数据和程序。与随机存取存储器&#xff08;RAM&#xff09;不同&#xff0c;ROM中的数据在断电后不会丢失&#xff0c;通常用于存储固件和系统启动程序。它的内容在制造时或通过…

GRE隧道协议学习笔记

使用场景 分布在不同地理位置的总公司和分公司怎么通过网络连接起来&#xff1f; 可以使用ISP网络连接。在豆包中可以看到如下回答通俗的讲就是运营商收费提供网络服务&#xff0c;有个人的有企业的&#xff0c;企业的很贵 为什么要使用GRE隧道 当然你也可以用其他隧道协议…

rocky9.2的lvs的NAT模式下的基本使用的详细示例

文章目录 前言什么是LVS?&#xff08;Linux Virtual Server&#xff09;LVS的组成1. 负载均衡器&#xff08;Load Balancer&#xff09;2. 后端服务器池&#xff08;Real Servers&#xff09;3. IPVS&#xff08;IP Virtual Server&#xff09;4. 调度算法&#xff08;Schedul…