React(四) 事件总线,setState的原理,PureComponent优化React性能,ref获取类组件与函数组件
文章目录
- 一、全局事件总线
- 二、setState的原理
- 1. 为什么要使用setState修改数据
- 2. setState的三种用法
- (1) 基本使用
- (2) 传入回调函数
- (3) setState是一个异步调用
- 3. setState为什么要设置成异步
- 二、PureComponent优化性能
- 1. React的diff算法以及Key的优化(扩展)
- (1) diff算法
- (2) 列表中的key属性
- 2. 引出问题:render函数的优化
- 3. shouldComponentUpdate
- 4. PureComponent与memo
- (1) 类组件
- (2) 函数式组件
- 5. PureComponent浅层监测
- 6. 实现PureComponent深层检测
- 三、ref获取元素或组件实例
- 1. ref获取原生DOM的三种方式
- 2. ref获取类组件实例
- 2. ref获取函数式组件里的元素
一、全局事件总线
安装第三方库:npm install hy-event-store
发送数据的组件触发事件:emit('事件名',参数)
// Son.jsxsendData () {// 触发事件,"tom", 100, 7.5是传递的参数eventBus.emit('getData', "tom", 100, 7.5)}render () {return (<div><h2>Son组件</h2><button onClick={this.sendData}>传递数据</button></div>)}
接收数据的组件绑定事件
绑定:xxx.on('事件名',绑定的函数,[this指向的值])
(this指向的值是可选的)
解绑:xxx.off('事件名',绑定的函数)
// App.jsxcomponentDidMount () {// 绑定事件,当getData事件被触发时,调用函数showDataeventBus.on('getData', this.showData)}componentWillUnmount () {// 解绑eventBus.off('getData', this.showData)}showData (name, nums, score) {console.log('showData', name, nums, score,);this.setState({ name, nums, score }) // 此时this指向undefined}
这里同样需要注意this的指向问题。这里有三种方式确定this指向
componentDidMount () {// 绑定事件// eventBus.on('getData', this.showData)// 方式一: on的第三个参数可指定this指向eventBus.on('getData', this.showData, this)// 方式二: 箭头函数eventBus.on('getData', (name, nums, score) => this.showData(name, nums, score))}// 方式三:es6的class filedsshowData = (name, nums, score) => {this.setState({ name, nums, score })}
二、setState的原理
1. 为什么要使用setState修改数据
Vue和React数据管理与渲染界面的区别:
因为Vue做了数据劫持,当数据变化时,Vue能够监听到数据的变化,然后底层的set方法调用了render()函数重新渲染页面。所以Vue用起来感觉是会自动渲染,不用我们手动调用render()函数。
而React没有数据劫持,如果通过this.state.msg = 'xxx'
来修改数据,Reac并不知道该数据发生变化,也就不会刷新页面。
如何让React得知数据发生变化?就是调用setState()
来修改数据,调用这个函数就相当于通知React数据发生了更新,需要重新渲染界面,React就会调用render()函数。
总结:
React并没有实现类似于Vue2中的Object.defineProperty或者Vue3中的Proxy的方式来监听数据的变化;我们必须通过setState来告知React数据已经发生了变化;
问:在组件中并没有实现setState的方法,为什么可以调用呢?
答:因为setState方法是从Component中继承过来的。
2. setState的三种用法
(1) 基本使用
setState({....})
this.state = {msg: 'Hello World',counter: 0}...// 点击按钮,调用changeText函数,修改msgchangeText () {this.setState({msg: 'Hello Money'})}
setState里创建了一个新对象赋给state。从内存的角度来看是这样的:
新对象里没有counter
,为什么新对象没有把旧对象覆盖掉呢?
底层其实是用了Object.assign(this.state,setState的新对象)
,把两个对象做了合并。然后在合适的时机再调用render()渲染。
(2) 传入回调函数
好处一: 可以在回调函数中编写对新state处理的逻辑
好处二: 当前的回调函数会将之前的state和props传递进来
changeText () {this.setState((state, props) => {console.log(state.msg, props) //打印 Hello World,空数组(因为props没值)return {msg: "你好啊, 李银河"}})
}
(3) setState是一个异步调用
changeText () {this.setState({ msg: "你好啊, 李银河" })console.log("------:", this.state.msg) // 打印的是Hello World,而不是新值
}
第三行比第二行先执行,说明setState是一个异步调用。
如果希望在数据更新之后(数据合并), 获取到对应的结果并执行一些逻辑代码
那么可以在setState中传入第二个参数: callback函数
changeText () {this.setState({ msg: "你好啊, 李银河" }, () => {console.log("++++++:", this.state.msg)})console.log("------:", this.state.msg)}
3. setState为什么要设置成异步
(1) setState设置为异步,可以显著提升性能
如果每次调用setState都进行一次更新,意味着render函数会被频繁调用,界面重新渲染,效率很低;最好的办法是获取到多个更新,之后进行批量更新。
changeCounter () {this.setState((state, props) => {console.log('第一次修改之前', state.count);return {count: state.count + 1}})this.setState((state, props) => {console.log(' 第二次修改之前', state.count);return {count: state.count + 1}})this.setState((state, props) => {console.log('第三次修改之前', state.count);return {count: state.count + 1}})}
render () {console.log('render函数被执行');...
}
三次setState
的调用,只调用了一次render函数。
如果发送的三个网络请求几乎同时返回结果,修改状态。则此时进行批量更新,只调用一次render,可显著提升性能。
(2) 如果同步更新了state,但未执行render函数,则state和props不能保持同步
state和props不能保持一致性,会在开发中产生很多问题。
加入18行代码是同步的,调用18行之后,12行的msg内容已变。但此时render函数还未调用,或者没执行完,导致传给子组件的props仍未更新。出现state和props不一致的情况。
二、PureComponent优化性能
1. React的diff算法以及Key的优化(扩展)
(1) diff算法
React的更新流程:
React在props或state发生改变时,会调用React的render方法,进而创建一颗不同的DOM树,然后进行新旧虚拟DOM的对比(diff算法):
如果新旧两棵虚拟DOM树进行完全比较,(也就是左侧div与右侧的所有节点比较,左侧h2与所有节点进行比较)。则算法的复杂度为O(n^2) (n是树中元素个数)
React对该算法的优化:
- 同层节点之间相互比较,不会跨层比较。
(左侧div只与右侧div进行比较,不会与下一层的h2,button进行比较) - 不同类型的节点,产生不同的树结构(后代元素全部做替换)
(如果左侧的div节点,与右侧同层的节点不一致,则以该节点为根节点的dom树,全部都进行更新,也就是div,h2,button都更换) - 开发中,通过key来指定哪些节点在不同的渲染下保持稳定。
(2) 列表中的key属性
和vue一样,key作为一个标识。
-
当在列表最后位置插入数据时,这种情况,有无key意义并不大
-
在前面插入数据
- 在没有key的情况下,所有的li都需要进行修改;
- 当子元素(这里的li)拥有 key 时,React 使用 key 来匹配原有树上的子元素以及最新树上的子元素;
-
key的注意事项:
(1) key应该是唯一的;
(2) key不要使用随机数(随机数在下一次render时,会重新生成一个数字)
(3) 使用index作为key,对性能是没有优化的;
2. 引出问题:render函数的优化
现有App、Son1、Son2三个组件
// 只关注render函数
class App extends Component {
...render () {console.log('App render');let { name, age } = this.statereturn (<div><h2>App组件---{name}---{age}岁</h2><Son1 /><Son2 /><button onClick={() => this.changeName()}>修改名字</button></div>)}
...
当App修改变量name时,App组件重新调用render函数,而其所有子组件的render函数也被重新调用了。
只修改App组件数据,所有组件却都需要重新render,重新新旧虚拟DOM对比(diff),性能必然是很低的。
子组件调用render的情况应该是:自己所依赖的数据发生改变时,再调用自己的render方法。
问题 :如何控制render是否被调用呢?
3. shouldComponentUpdate
生命周期函数shouldComponentUpdate(简称SCU)
-
该函数有两个参数
参数一:nextProps
,最新的props属性
参数二:nextState
,最新的state属性 -
返回值是布尔类型
返回值为true,调用render方法;
返回值为false,不调用render方法
默认返回true
(1) 问题1:如果修改后的值和修改前一样,则不需要调用render函数
// App组件中:shouldComponentUpdate (nextProps, nextState) {if (this.state.name !== nextState.name || this.state.age!== nextState.age) {return true}//nextState.name还是tom,nextState.age还是10,则返回flasereturn false}
(2) 问题2:子组件没用到父组件的数据,则父组件更新时,子组件无需再调用render函数。
现将App中的name传给子组件Son1,age传给子组件Son2
render () {console.log('App render');let { name, age } = this.statereturn (<div><h2>App组件---{name}---{age}岁</h2><Son1 name={name} /><Son2 age={age} /><button onClick={() => this.changeName()}>修改名字</button><button onClick={() => this.changeAge()}>修改年龄</button></div>)}
Son1和Son2分别设置SCU
当点击修改名字时,Son2的render不被调用。修改年龄时,Son1的render不被调用
4. PureComponent与memo
如果所有的类,都需要手动来实现 shouldComponentUpdate,工作量很多,而且如果需要判断的数据很多,if语句也会很长。
此时我们可以使用React提供的PureComponent 和memo。这两个分别用于类组件和函数式组件
(1) 类组件
对于类组件,继承PureComponent
即可,而不是继承Component
,
(2) 函数式组件
函数式组件无法继承,使用memo包裹即可
import { memo } from "react";
const Son3 = memo(function (props) {return (< div ><h3>Son3:{props.age}</h3></div >)
})
export default Son3
5. PureComponent浅层监测
PureComponent的底层是浅层监测数据是否发生变化。
比如在这个页面中,点击添加按钮,需要添加一本书
this.state = {books: [{ name: "你不知道JS", price: 99, count: 1 },{ name: "JS高级程序设计", price: 88, count: 1 },{ name: "React高级设计", price: 78, count: 2 },{ name: "Vue高级设计", price: 95, count: 3 },],msg: 'HelloWorld'}
// 添加按钮的回调函数为addNewBook () {const newBook = { name: "Vue高级设计", price: 95, count: 1 }// 方式一:当类组件继承自Component时可以,(虽然可以,但不推荐)// 但继承于Purecomponent时,这种修改方式行不通的this.state.books.push(newBook)this.setState({ books: this.state.books })}
因为books是引用数据类型,它的值是地址值,虽然该数组确实添加了一个元素,但是books地址值未变,所以PureComponent监测不到。
正确打开方式是:
addNewBook () {const newBook = { name: "Angular高级设计", price: 85, count: 1 }// 方式二: 浅拷贝let books = [...this.state.books]books.push(newBook)this.setState({ books: books })}
浅拷贝之后的books
地址值和this.state.books
的地址值不一样(内容一样); 所以 this.setState({ books: books })
相当于给state里的books赋值了新值,PureComponent就能监测到了。
6. 实现PureComponent深层检测
如果要修改books里面的count值,也需要进行依次浅拷贝然后再修改。浅拷贝的目的是让books的地址值改变,从而让组件能够监测的到数据变化,调用render函数。
changeCount (index) {// this.state.books[index] += 1let books = [...this.state.books]books[index].count += 1this.setState({ books: books })}
结合5里画的内存图,其实可以看出第2行与第5行改的是同一块内存。
三、ref获取元素或组件实例
1. ref获取原生DOM的三种方式
方式一:在元素上用ref打标识:<h1 ref='title'>
方式二:调用createRef()
函数,先创建一个ref标识,然后再元素上绑定这个标识。
方式三:在标签上通过ref传递一个回调函数,参数值就是当前元素。
import React, { createRef, PureComponent } from 'react'
export class App extends PureComponent {// 获取原生dom的三种方式constructor() {super()// 方式二:先创建一个标识this.hwRef = createRef()// 方式三this.getRef = null}getDOM (el) {// 方式一:被废弃console.log(this.refs.title);// 方式二:.current获取到当前的元素,但是若在很多标签上都标识hwRef,// .current还是只能获取到一个元素console.log(this.hwRef.current);// 方式三console.log(this.getRef);}render () {return (<div>{/* 方式一: */}<h1 ref='title'>App组件</h1>{/* 方式二:绑定事先创建好的标识 */}<h2 ref={this.hwRef}>HelloWorld</h2>{/* 方式三:这里的el就是dom元素 */}<h3 ref={el => this.getRef = el}>身体健康</h3><button onClick={e => this.getDOM()}>点击获取Dom元素</button></div>)}
}
2. ref获取类组件实例
创建子类Son:
export class Son extends PureComponent {// 实例方法showInfo () {console.log('I am 子组件');}render () {return (<h2>Son组件</h2>)}
}
父类获取到子组件的组件实例后,可以调用子组件的实例方法:
export class App extends PureComponent {constructor() {super()// 1. 创建ref对象this.childRef = createRef()}getDOM () {// 3. 获取子组件实例,并调用子组件的实例方法console.log(this.childRef.current);this.childRef.current.showInfo()}render () {return (<div>{/* 2. 创建的ref对象绑定在子组件上 */}<Son ref={this.childRef} /><button onClick={e => this.getDOM()}>点击获取Dom元素</button></div>)}
}
2. ref获取函数式组件里的元素
因为函数式组件没有组件实例,上述的方式获取不到函数组件
function Son2 () {return (<h2>Son2组件</h2>)
}<Son2 ref={this.childRef} />
console.log(this.childRef.current); // 打印出来为null
对于函数式组件,我们可以通过ref获取到组件里的某个元素,比如:<h2>Son2组件</h2>
需要借助forwardRef
,这样函数组件可以接收两个参数,一个是props
,一个是ref
import React, { createRef, PureComponent, forwardRef } from 'react'
// 这里的ref
const Son2 = forwardRef(function (props, ref) {return (<h2 ref={ref}>Son2组件</h2>)
})
console.log(this.childRef.current); // <h2>Son2组件</h2>
相关文章:

React(四) 事件总线,setState的原理,PureComponent优化React性能,ref获取类组件与函数组件
文章目录 一、全局事件总线二、setState的原理1. 为什么要使用setState修改数据2. setState的三种用法(1) 基本使用(2) 传入回调函数(3) setState是一个异步调用 3. setState为什么要设置成异步 二、PureComponent优化性能1. React的diff算法以及Key的优化(扩展)(1) diff算法(2…...

Java学习-JVM
目录 1. 基本常识 1.1 JVM是什么 1.2 JVM架构图 1.3 Java技术体系 1.4 Java与JVM的关系 2. 类加载系统 2.1 类加载器种类 2.2 执行顺序 2.3 类加载四个时机 2.4 生命周期 2.5 类加载途径 2.6 双亲委派模型 3. 运行时数据区 3.1 运行时数据区构成 3.2 堆 3.3 栈…...
leed认证分几个级别
LEED(Leadership in Energy and Environmental Design)认证是一个评估建筑项目可持续性的严格框架,其级别主要分为以下四个: LEED认证(Certified):这是最低级别的认证,要求建筑项目…...

3.C++经典实例-计算一个数的阶乘
阶乘(factorial)是基斯顿卡曼于1808年发明的运算符号,用于表示一个正整数n的所有小于及等于该数的正整数的积。自然数n的阶乘写作n!。例如,5的阶乘表示为5! 1 2 3 4 5 120。 阶乘在数学和计算机科学中有广泛的应用。例如…...

深入理解Qt中的QTableView、Model与Delegate机制
文章目录 显示效果QTableViewModel(模型)Delegate(委托)ITEM控件主函数调用项目下载在Qt中,视图(View)、模型(Model)和委托(Delegate)机制是一种非常强大的架构,它们实现了MVC(模型-视图-控制器)设计模式。这种架构分离了数据存储(模型)、数据展示(视图)和数据操作(委托),使…...

解读《ARM Cortex-M3 与Cortex-M4 权威指南》——第1章 ARM Cortex-M处理器简介
1. 三级流水线设计 解释:三级流水线设计意味着处理器在执行指令时可以同时处理多个步骤。这些步骤通常包括取指(Fetch)、译码(Decode)和执行(Execute)。好处:这种设计提高了指令的执行效率,使得处理器能够在每个时钟周期内完成更多的工作,从而提升整体性能。2. 哈佛总…...

java集合类的框架体系
1.集合的好处 相比数组,他可以存储多种类型的元素,并且可以动态新增; 2. 集合分类 3.Collection接口 3.1常用方法 3.2迭代器-遍历 collection接口继承了Interable接口,collection的子类可以使用迭代器; 注意事项…...

基于SpringBoot+Vue+Uniapp家具购物小程序的设计与实现
详细视频演示 请联系我获取更详细的演示视频 项目运行截图 技术框架 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念,提供了一套默认的配置,让开发者可以更专注于业务逻辑而…...

什么是模糊测试?
近年来,随着信息技术的发展,各种新型自动化测试技术如雨后春笋般出现。其中,模糊测试(fuzz testing)技术开始受到行业关注,它尤其适用于发现未知的、隐蔽性较强的底层缺陷。这里,我们将结合AFL开…...

3.C++经典实例-奇数还是偶数
要判断一个数是奇数还是偶数,只需要判断这个数是否能被2整除即可,如果要判断是否能整除,则要判断当前数除以2的余数是否为0,在C中,余数,使用%号,因此,程序为: #include …...

真牛啊!全球人工智能标准教科书,斯坦福、麻省理工、加州大学等十多所顶尖机构为它点赞!!
这本《人工智能:计算代理基础》堪称全球人工智能标准教科书!它因其前沿的技术融合、丰富的实践案例以及理论深度与实践并重的特点而成为人工智能领域的热门书籍。 该书已经赢得了斯坦福大学、佐治亚理工学院、谷歌大脑、麻省理工学院、加州大学、微软研究…...
Android——通过MediaStore查询图片
查询图片: private void loadImageList() {String[] columns new String[]{MediaStore.Images.Media._ID, // 编号MediaStore.Images.Media.TITLE, // 标题MediaStore.Images.Media.SIZE, // 文件大小MediaStore.Images.Media.DATA, // 文件路径};Cursor cursor g…...

手写Spring IOC-简易版
目录 项目结构entitydaoIUserDaoUserDaoImpl serviceIUserServiceUserServiceImpl ApplicationContext 配置文件初始化 IOC 容器RunApplication 注解初始化 IOC 容器BeanAutowired Reference 项目结构 entity User Data NoArgsConstructor AllArgsConstructor Accessors(chai…...

【算法题】62. 不同路径(LeetCode)
【算法题】62. 不同路径(LeetCode) 1.题目 下方是力扣官方题目的地址 62. 不同路径 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图…...
【VUE】Vue中的data属性为什么是一个函数而不是一个对象
在 Vue.js 中,组件的 data 属性可以是一个对象或者一个函数但通常建议将其设置为函数。这是因为组件可能会被多次使用,如果 data 是一个普通对象,那么该对象会被所有实例共享,导致数据混乱。将 data 设置为一个函数可以保证每个组…...
ddos攻击介绍和排查方法
一、DDoS攻击介绍 DDoS攻击,全称为分布式拒绝服务攻击(Distributed Denial of Service Attack),是一种常见的网络攻击手段。它通过利用多个计算机系统向目标服务器、服务或网络发送大量请求,导致目标无法处理正常流量…...
git clone --single-branch 提升效率
git clone --single-branch 是一个Git命令,用于从远程仓库中仅克隆单个分支到本地仓库。这个命令在软件开发中非常有用,尤其是在需要特定分支的代码而无需整个仓库的情况下。 基本用法 git clone --single-branch 命令的基本语法如下: git…...

代码随想录算法训练营第十天|1. 两数之和,第454题.四数相加II
文档讲解:代码随想录 难度:一般嗷~~ 1. 两数之和 力扣题目链接(opens new window) 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 你可以假设每种输入只会对…...

龙迅LT8911EX LVDS转EDP 点屏,大批量出货产品
龙迅LT8911EX描述: Lontium LT8911EX是LVDS到eDP转换器,具有单端口或双端口可配置的LVDS接收器,有1个时钟通道和最多8个数据通道,每个数据通道最大运行1.2Gbps,最大输入带宽为9.6Gbps。转换器将输入LVDS数据去序列化&…...
浅谈Oracle之游标
一、基本介绍 在Oracle数据库中,游标(Cursor)是一种强大的工具,用于逐行处理查询结果集。然而,游标的使用需要谨慎,因为不当的使用可能会导致性能问题。 二、最佳实践和优化技巧 尽量避免使用游标…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练
前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1):从基础到实战的深度解析-CSDN博客,但实际面试中,企业更关注候选人对复杂场景的应对能力(如多设备并发扫描、低功耗与高发现率的平衡)和前沿技术的…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...

什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...

ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...
C++.OpenGL (14/64)多光源(Multiple Lights)
多光源(Multiple Lights) 多光源渲染技术概览 #mermaid-svg-3L5e5gGn76TNh7Lq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-3L5e5gGn76TNh7Lq .error-icon{fill:#552222;}#mermaid-svg-3L5e5gGn76TNh7Lq .erro…...