React 理解 re-render 的作用、概念,并提供详细的例子解释

一、什么是 re-render

在 React 中 re-render(重新渲染) 是经常发生的行为,主要确保视图要时刻保持最新的数据来呈现。

但每次发生 re-render 也是有代价的,比如数据状态、focus 焦点、表单数据、都得重置,
遇到代码复杂一点,还给性能带来不必要的消耗。

React 的 re-render 是一个很核心的概念,我们必须得掌握它,否则出现问题将无从下手。

下面我将会介绍触发 re-render 的场景,以及如何避免 re-render

二、什么场景会触发 re-render

前言:re-render 只会影响自身和 children,如果 children 还有 children 则继续影响,以此类推。
理解这点很重要,它将会作为后续如何避免 re-render 的解题思路。

提示:此 children 非 props.children

2.1 parent

parent 组件发生 re-render 时,自身与 children(子组件) 都会受影响(上面已提到过)。

代码如下:

import ReactDOM from 'react-dom/client';
import { useState, } from 'react';
const root = ReactDOM.createRoot(document.getElementById("root"));
const Parent = () => {const [parentName, setParentName] = useState('Jack')const ChildComponent = () => {console.log('Init child Component.')return <><div>I am child.</div></>}return <><div><span>Parent name: {parentName}</span><button onClick={() => setParentName('Tony')}>Change Parent'name.</button></div><ChildComponent/></>
}
root.render(<Parent/>
);

关系如图:
在这里插入图片描述

效果:
请添加图片描述

2.2 state

state 数据发生变化时,那些引用过 state 的组件都会发生 re-render

提示: 其实和 parent 没啥两样,上面的 parent 演示只是为了加深理解 re-render 的渲染机制。

下面我将以子组件自身更改 state 来完成自身 re-render 的演示。

代码如下:

import ReactDOM from 'react-dom/client';
import { useState, } from 'react';
const root = ReactDOM.createRoot(document.getElementById("root"));
const Parent = () => {const [parentName, setParentName] = useState('Jack')const ChildComponent = () => {console.log('Init child Component.')return <><div>I am child.</div><button onClick={() => setParentName('Tony')}>A child can change his parent'name.</button></>}return <><div><span>Parent name: {parentName}</span></div><ChildComponent/></>
}
root.render(<Parent/>
);

关系如图:
在这里插入图片描述
效果:
请添加图片描述

2.3 props

当组件的 props 数据发生变化时触发 re-render

代码如下:

import ReactDOM from 'react-dom/client';
import { useState, } from 'react';
const root = ReactDOM.createRoot(document.getElementById("root"));
const Parent = () => {const [parentName, setParentName] = useState('Jack')const ChildComponent = (props) => {console.log('Init child Component.')return <><div>I am child. My parent'name is {props.parentName}</div></>}return <><div><span>Parent name: {parentName}</span><button onClick={() => setParentName('Tony')}>Change Parent'name.</button></div><ChildComponent parentName={parentName}/></>
}
root.render(<Parent />
);

关系如图:
在这里插入图片描述

你可能注意到了,这触发 re-render 还不是因为更改了 state ? 好像跟上面的例子没啥两样啊?
因为 props 本身就支持传递 state 呀,这块先别着急,目前先把它当成 props 发生变化时,组件会执行 re-render 即可 ,等到后面的避免 re-render 章节自然就明白了。

效果:
请添加图片描述

2.4 context

当 context 数据发生变化时,那些引用 context 的组件都会触发 re-render

代码如下:

import ReactDOM from 'react-dom/client';
import { useState, createContext, useContext, } from 'react';
const root = ReactDOM.createRoot(document.getElementById("root"));
const ThemeContext = createContext(null)
const Parent = () => {const [theme, setTheme] = useState({mode: 'light'})const ChildComponent = () => {const theme = useContext(ThemeContext)console.log('Init child component.')return <><div>The current mode is: {theme.mode}</div></>}const onChangeTheme = () => {setTheme(() => ({...theme,mode: theme.mode === 'light' ? 'dark' : 'light'}))}return <><ThemeContext.Provider value={theme}><button onClick={onChangeTheme}>Change mode</button><ChildComponent/></ThemeContext.Provider></>
}
root.render(<Parent />
);

关系如图:
在这里插入图片描述

你可能注意到了,这触发 re-render 还不是因为更改了 state ? 好像跟上面的例子没啥两样啊?
因为 context 本身就支持传递 state 呀,这块先别着急,目前先把它当成 context 发生变化时,组件会执行 re-render 即可 ,等到后面的避免 re-render 章节自然就明白了。

效果:
请添加图片描述

提示:还没用过 context 的可以先阅读React & 解释常见的 hooks: useState / useRef / useContext / useReducer 再回头看本文。

三、如何优雅地避免 re-render

前言:在理解 re-render 的触发场景后,对如何避免 re-render 将会是一件既有趣又有挑战性的事情。
“挑战性”皆指容易犯“过度/错误 地避免 re-render,这是每个新手基本都会犯的错甚至老手,对于刚学 React 的同学也不必担心,因为它并不是一俩天就能搞定的,得不断的实践和积累才能慢慢减少。

废话不多说,咋们开始进入正文。

3.1 提取 state

既然 state 发生变化时,组件自身和 children 都会 re-render ,我们可以把变更 state 代码单独抽取一个组件来维护。

代码如下:

import ReactDOM from 'react-dom/client';
import { useState, } from 'react';
const root = ReactDOM.createRoot(document.getElementById("root"));
const Parent = () => {const BaseComponent = () => {const [parentName, setParentName] = useState('Jack')return <><div><span>Parent name: {parentName}</span><button onClick={() => setParentName('Tony')}>Change Parent'name.</button></div></>}const ChildComponent = () => {console.log('Init child Component.')return <><div>I am child.</div></>}return <><BaseComponent /><ChildComponent/></>
}
root.render(<Parent />
);

关系如图:
在这里插入图片描述
效果:
请添加图片描述

3.2 组件以 props / children 的方式传递

children 其实是 props 的一部分,而 props 的其它字段也可作为组件,因此我将它俩单独放一起做演示。

回到正题,在不想分离 state 的情况下,我们可以将组件以 props 的形式传递,一旦 state 发生变化时,若组件自身的 props 未发生变化就不会触发 re-render,本质上也是一种抽离的思想。

代码如下:

import ReactDOM from 'react-dom/client';
import { useState, } from 'react';
const root = ReactDOM.createRoot(document.getElementById("root"));
// 方式1:采用 children 作为组件。
import ReactDOM from 'react-dom/client';
import { useState, } from 'react';
const root = ReactDOM.createRoot(document.getElementById("root"));
const Parent = () => {const BaseComponent = ({children}) => {const [parentName, setParentName] = useState('Jack')return <><div><span>Parent name: {parentName}</span><button onClick={() => setParentName('Tony')}>Change Parent'name.</button></div>{children}</>}const ChildComponent = () => {console.log('Init child Component.')return <><div>I am child.</div></>}return <><BaseComponent><ChildComponent/></BaseComponent></>
}
root.render(<Parent />
);// 方式2:采用 props 其它字段作为组件传递。
const Parent = () => {const BaseComponent = ({ bottom }) => {const [parentName, setParentName] = useState('Jack')return <><div><span>Parent name: {parentName}</span><button onClick={() => setParentName('Tony')}>Change Parent'name.</button></div>{bottom}</>}const ChildComponent = () => {console.log('Init child Component.')return <><div>I am child.</div></>}return <><BaseComponent bottom={ChildComponent()}></BaseComponent></>
}
root.render(<Parent />
);

关系如图:
在这里插入图片描述
效果:
请添加图片描述

3.3 使用 memo + useMemo/useCallback

memo 用于缓存组件,useMemo 用于缓存数据,useCallback 用于缓存函数。

提示1: useMemo 也能模拟 useCallback ,但俩者的用法有着细微差别,后面会提到。
提示2:memo 的底层实现逻辑也需要维护成本,建议在不能使用 3.1、3.2 的方式情况下再来考虑 memo

3.3.1 memo

当我们无法让组件分离 state 或以 props/children 的方式时,memo 可以派上用场,当组件内引用 props/state/context 发生变化时才会发生re-render

代码如下:

import ReactDOM from 'react-dom/client';
import { useState, memo } from 'react';
const root = ReactDOM.createRoot(document.getElementById("root"));
const ChildMemo = memo(function ChildMemo({ name }) {console.log('Init component.')return <><h3>Child's name is: {name}</h3></>
})
const Parent = () => {const [childName, setChildName] = useState('Lucy')const [parentName, setParentName] = useState('Jack')return <><div><div><input type="text" value={parentName} placeholder='Please enter your name' onChange={(e) => setParentName(e.target.value)} /></div><div><button onClick={() => setChildName('Tony')}>Set child name</button></div></div><ChildMemo name={childName} /></>
}
root.render(<Parent />
);

关系如图:
在这里插入图片描述

效果:
请添加图片描述

3.3.2 useMemo

上面讲的是缓存组件,那在什么情况下需要缓存数据呢?先思考下这段代码:
在这里插入图片描述
知道引用类型的同学应该都知道,当声明两个对象(数据相同)进行比较时,
obj1 === obj2 总是等于 false,于是每次 re-render 的时候,这个函数实际上就会被重新创建一次,因此这里的 memo 将会一直无效。

请添加图片描述

解决方法就是使用 useMemo 来缓存这份对象:
在这里插入图片描述
其中 useCallback 的第二个参数是可选项,可以指定某个依赖项发生变化时再触发 re-render

具体代码如下:

import ReactDOM from 'react-dom/client';
import { useState, memo, useMemo } from 'react';
const root = ReactDOM.createRoot(document.getElementById("root"));
const ProfileMemo = memo(function Profile({info}) {console.log('Init component')return <><div>Address: {info.address}</div></>
})const Parent = () => {const [parentName, setParentName] = useState('Jack')const info = useMemo(() => ({address: 'London'}), [])return <><div>Parent name: {parentName}</div><button onClick={() => setParentName('John')}>Change parent's name</button><ProfileMemo info={info} /></>
}
root.render(<Parent />
);

效果:
请添加图片描述

3.3.3 useCallback

在什么情况下需要缓存函数呢?首先得明确一点,函数也是对象的一部分,因此两个同样的函数比较时永远为 false ,思考下这段代码:
在这里插入图片描述
这里的 ProfileMemo 组件将因为函数的不等价导致再次被 re-render
请添加图片描述

此时缓存函数 useCallback 就可以解决啦,改造后关系图如下:
在这里插入图片描述
代码如下:

import ReactDOM from 'react-dom/client';
import { useState, memo, useMemo, useCallback } from 'react';
const root = ReactDOM.createRoot(document.getElementById("root"));
const ProfileMemo = memo(function Profile({info}) {console.log('Init component')return <><div>Address: {info.address}</div></>
})const Parent = () => {const [parentName, setParentName] = useState('Jack')const info = useMemo(() => ({address: 'London'}), [])const handSubmit = useCallback(() => {console.log('Save profile.')}, [])return <><div>Parent name: {parentName}</div><button onClick={() => setParentName('John')}>Change parent's name</button><ProfileMemo handSubmit={handSubmit} info={info} /></>
}
root.render(<Parent />
);

useCallback 的第二个参数和 useMemo 是一样的,可以指定某个依赖项发生变化时再触发 re-render

效果:
请添加图片描述

3.3.4 useMmeo 模拟 useCallback

useMemo 除了缓存数据也支持缓存函数,以下两种写法的作用是一样的:

const handleSumit = useMemo(() => (return () => {console.log('Save profile')	}
), [])
const handSubmit = useCallback(() => {console.log('Save profile.')}, [])

唯一区别是, useMemo 共包裹两层函数,官方也建议 useCallback 作为缓存函数首选。

3.4 提取 context

经过前几章的学习,对如何避免 re-render 我们其实已经有了一个框架和概念。
比如 3.1 章节,我们也可以将变化 context 的部分提取出来,与其它组件保持隔离。

具体效果不演示了,大家可自行练习。

四、避免 re-render 的几个注意事项

4.1 memo 不要与变更父级 state 数据的子组件放一起

思考下这段代码:当子组件更改外部的 state 时,ChildMemo 组件是否会触发 re-render?
在这里插入图片描述

答案是会的!而且 input 的每一次更改外部 state 时,ChildMemo 都将被重新 re-render

为什么?

这里会涉及到作用域的概念,当前 BaseCmpstate 并不在同个作用域,它改变了外部 state 作用域的值,
根据 2.2 章节讲到,当 state 数据发生变化时,那些引用过 state 的组件都将被 re-render,而 ChildMemo 刚好与 BaseCmp 处于同个作用域,因此也就一并被 re-render

解决的方式有几种:

  1. 将 ChildMemo 提取出来,与 BaseCmp 隔离。
    在这里插入图片描述

  2. 将外部的 state 转成内部 state ,保持同级作用域。
    在这里插入图片描述

  3. 自行发挥~

结论:同一作用域的 state 发生变化时,memo 在没有引入该 state 的情况下不会受到影响,
若 memo 放在引入外部 state 的子组件时,当 state 发生变化时则受到影响,它并不能阻止 re-render

以上便是有关 re-render 的所有内容,如有问题,欢迎补充。


完!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1544529.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Linux ping c实现

linux下ping程序的c实现 #include <stdio.h> #include <stdlib.h> #include <sys/socket.h> #include <netinet/in.h> #include <stdint.h> #include <netdb.h> #include <arpa/inet.h> #include <unistd.h> #include <st…

LoongArch 个人赛一级评测(前递旁路+load阻塞)

目录 LoongArch 个人赛一级评测&#xff08;前递旁路load阻塞&#xff09;声明测试说明 代码修改thinpad_top.vconver_ram.vIF_stage 测试自动评测在线实验 踩坑记录读写使能控制inout类端口的使用方法Vivado生成Bit流文件时出现[Synth 8-91] ambiguous clock in event control…

file zilla server安装以后,client连接,账号登录成功,但是读取目录失败的处理

1、下载 2、连接 开始一直报错&#xff0c;server1.9.1的版本&#xff0c;后来直接下载了汉化版本进行安装&#xff1a; 服务端 - FileZilla中文网 3、报错&#xff1a;登录正常&#xff0c;但是读取目录失败&#xff0c;一开始也是这个错误&#xff0c;后来改成安装低版本的&a…

python之装饰器、迭代器、生成器

装饰器 什么是装饰器&#xff1f; 用来装饰其他函数&#xff0c;即为其他函数添加特定功能的函数。 装饰器的两个基本原则&#xff1a; 装饰器不能修改被装饰函数的源码 装饰器不能修改被装饰函数的调用方式 什么是可迭代对象&#xff1f; 在python的任意对象中&#xff…

Mac使用技巧-来自苹果专人在线辅导服务3

真的太喜欢上苹果专人在线辅导课程了&#xff01;每次感觉都满满收获&#xff01; 目录 一、手势 1.1三指拖移 1.2四指上推 1.3五指快速进入启动台 二、程序坞里的app 1.如何将程序坞中不需要/不常用的app移除&#xff1f; 2.当鼠标放在将程序坞中app时&#xff0c;图标…

到底该不该做副业?这些真相你必须知道!零基础入门到精通,收藏这一篇就够了

天哪&#xff01;根据最新调查显示&#xff0c;超过60%的职场人士正在考虑或已经开始从事副业。 那么&#xff0c;究竟该不该做副业&#xff1f; 第一部分&#xff1a;副业的好处 1. 增加收入 做副业最直接的好处就是可以增加收入。 在经济压力日益增大的今天&#xff0c;…

STMCubeMx——C8T6的串口调试、接收与发送

一、stmCubeMX串口通信的步骤 1、新建一个文件&#xff0c;选择自己的芯片 2、配置时钟 3、配置串口 串口的模式解析可以跳转到以下文章查看 stmCubemx——配置串口时的几种模式-CSDN博客在STM32CubeMX中配置串口&#xff08;USART或UART&#xff09;时&#xff0c;可以设置…

Python连接Kafka并收发数据

目录 一、Kafka 二、发送端&#xff08;生产者&#xff09; 三、接收端&#xff08;消费者&#xff09; 一、Kafka Apache Kafka 是一个开源流处理平台&#xff0c;由 LinkedIn 开发&#xff0c;并于 2011 年成为 Apache 软件基金会的一部分。Kafka 广泛用于构建实时的数据…

soul怎么改ip地址归属地

在数字化时代&#xff0c;社交应用已成为人们日常生活中不可或缺的一部分。Soul&#xff0c;作为一款深受年轻人喜爱的社交APP&#xff0c;以其独特的灵魂社交理念和丰富的互动功能吸引了大量用户。然而&#xff0c;在使用Soul的过程中&#xff0c;不少用户会关注到一个问题——…

详解Web测试和APP测试的区别

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 最近听到有些朋友说&#xff0c;移动端要比web端稍微难一些&#xff0c;涉及到的细节笔记要多&#xff0c;有转去做web测试的想法&#xff0c;看看在具体测试的…

华为全联接大会2024 | 一文回顾华为云开发者联盟重磅干货

目录 华为开发者空间预置更多工具资源&#xff0c;带来丰富场景案例 携手鲲鹏、鸿蒙、昇腾等根生态&#xff0c;使能开发者创新 学习体验、内容体系全面升级&#xff0c;助力开发者高效学习根技术 参与丰富线上体验活动&#xff0c;赢取精美礼品 在刚刚结束的华为全联接大会…

基于Netty框架的云快充协议+云快充1.5协议+云快充桩直连+桩直连协议

云快充协议云快充1.5协议云快充1.6云快充协议开源代码云快充底层协议云快充桩直连桩直连协议充电桩协议云快充源码 介绍 云快充协议云快充1.5协议云快充1.6云快充协议开源代码云快充底层协议云快充桩直连桩直连协议充电桩协议云快充源码 软件架构 1、提供云快充底层桩直连协…

智能Ai语音机器人的应用价值有哪些?

随着时间的推移&#xff0c;人工智能的发展越来越成熟&#xff0c;智能时代也离人们越来越近&#xff0c;近几年人工智能越来越火爆&#xff0c;人工智能的应用已经开始渗透到各行各业&#xff0c;与生活交融&#xff0c;成为人们无法拒绝&#xff0c;无法失去的一个重要存在。…

Java项目: 基于SpringBoot+mybatis+maven医疗病历交互系统(含源码+数据库+毕业论文)

一、项目简介 本项目是一套基于SpringBootmybatismaven医疗病历交互系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简…

全球网安行业缺少350万安全专家? 志愿你别乱填,缺的是专家,不是0经验的牛马

0x00 首先是这个所谓的“高需求”。企业们天天喊着缺人&#xff0c;但当你打开招聘网站&#xff0c;看到的全是“三年经验起步”、“高级专家”。黑人问号&#xff1f;这是在逗我吗&#xff1f;都只想要高级专家&#xff0c;拒绝新手及应届生&#xff0c;没有新手&#xff0c;哪…

舒服了!学大模型必看的学习书籍来了

最近整理了日前市面上一大波大模型的书&#xff0c;已经打包成pdf了&#xff0c;大家有需要的&#xff0c;可以自行添加获取&#xff0c;纯福利&#xff0c;无套路&#xff0c;添加后说明是哪本书&#xff0c;会直接给大家&#xff01;&#xff08;文末获取&#xff09; 部分书…

用于体积医学图像分割的跨视角差异依赖网络|文献速递--基于多模态-半监督深度学习的病理学诊断与病灶分割

Title 题目 Cross-view discrepancy-dependency network for volumetric medical image segmentation 用于体积医学图像分割的跨视角差异依赖网络 01 文献速递介绍 医学图像分割的目标是通过为每个像素分配语义类别&#xff0c;从原始图像中描绘出受试者的解剖结构&#x…

Activiti7《第九式:破气式》——流畅驱动工作流进程。面试题大全

冲冲冲&#xff01;开干 这篇文章将分为九个篇章&#xff0c;带你逐步掌握工作流的核心知识。“破气式”&#xff0c;代表着工作流中的 无形之力&#xff0c;它是贯穿整个流程的 关键驱动 不知不觉已经到了独孤九剑最后一式了&#xff0c;我相信到这里之后各位都已经出神入化…

华为HarmonyOS灵活高效的消息推送服务(Push Kit) -- 10 推送实况窗消息

场景介绍 实况窗是一种帮助用户聚焦正在进行的任务&#xff0c;方便快速查看和即时处理的通知形态。有关实况窗简介、权限申请、开放场景、设计规范等说明&#xff0c;请参见Live View Kit简介。 通过Push Kit发送的实况窗消息支持三种操作类型&#xff0c;分别是&#xff1a…

云手机推荐:五款热门云手机测评!

在云手机市场中&#xff0c;各个品牌层出不穷&#xff0c;让人难以选择。为了帮助你更好地找到适合的云手机应用&#xff0c;我们整理了五款最受欢迎的云手机进行测评。2024年&#xff0c;哪款云手机是你的不二之选&#xff1f;且慢下结论&#xff0c;看看这五款云手机的真实表…