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)是一种强大的工具,用于逐行处理查询结果集。然而,游标的使用需要谨慎,因为不当的使用可能会导致性能问题。 二、最佳实践和优化技巧 尽量避免使用游标…...
微元理论的数学化演算
一、理论思想总结(一段式,完全还原你最新表述)本理论借用希格斯标量场解释统标量场为宇宙唯一本源,在微观尺度下,标量场中两个无质量特性的标量子,当其间距大于普朗克作用量 h 所界定的临界尺度时ÿ…...
Go Context 生命周期设计
Go Context 生命周期设计:高效管理请求与资源 在Go语言中,Context是管理请求生命周期和跨协程控制的核心工具。它不仅能传递请求范围的数据,还能优雅地处理超时、取消和资源释放,成为高并发场景下的必备机制。本文将深入探讨Cont…...
C++信号量实战:如何用Semaphore解决多线程打印ABC问题(附完整代码)
C信号量实战:如何用Semaphore解决多线程打印ABC问题(附完整代码) 多线程编程中,同步机制的选择往往决定了程序的性能和可靠性。信号量(Semaphore)作为一种经典的同步原语,在解决特定类型的问题时…...
React-md-editor性能优化:如何提升大型文档编辑体验
React-md-editor性能优化:如何提升大型文档编辑体验 【免费下载链接】react-md-editor A simple markdown editor with preview, implemented with React.js and TypeScript. 项目地址: https://gitcode.com/gh_mirrors/re/react-md-editor React-md-editor…...
OpenClaw技能开发入门:为SecGPT-14B编写自定义漏洞检测模块
OpenClaw技能开发入门:为SecGPT-14B编写自定义漏洞检测模块 1. 为什么需要自定义漏洞检测技能 去年在一次内部红队演练中,我遇到了一个典型问题:现有扫描工具对新型API漏洞的检测覆盖率不足,而手动验证每个可疑端点又极其耗时。…...
Java版Playwright实战:从零开始搭建自动化测试框架(含完整代码示例)
Java版Playwright实战:从零开始搭建自动化测试框架(含完整代码示例) 在当今快节奏的软件开发环境中,自动化测试已成为保障产品质量不可或缺的一环。对于Java开发者而言,Playwright以其跨浏览器支持、现代化API设计和出…...
OpenClaw故障模拟:gemma-3-12b-it在断网环境下的降级处理方案
OpenClaw故障模拟:gemma-3-12b-it在断网环境下的降级处理方案 1. 为什么需要关注断网场景下的容灾设计 上周我在调试一个基于OpenClaw的自动化日报生成系统时,遇到了一个意外情况:网络突然中断导致整个流程卡死。这让我意识到,在…...
嵌入式智能饮水机设计:STM32与语音交互实践
1. 项目背景与需求分析作为一名嵌入式开发工程师,我最近完成了一个专门为视障人士设计的智能饮水机项目。这个项目的灵感来源于我的一位视障朋友在使用传统饮水机时遇到的种种不便——他常常因为无法判断水温而被烫伤,或者因为不知道水杯是否对准出水口而…...
linux (CentOS 7) 一次性安装中文手册的完整命令
一,一次性第一步:安装 CentOS 7 专属的中文语言包 man 手册包yum install -y kde-l10n-Chinese man-pages-zh-CN第二步:刷新语言环境,让配置生效export LANGzh_CN.UTF-8第三步:验证,直接执行中文 man lsma…...
别再只用Speedtest了!自建LibreSpeed测速站,监控家庭宽带/公司内网真实表现
自建网络测速站:用LibreSpeed打造精准带宽监控系统 每次看到运营商宣传的"千兆宽带",你是否怀疑过实际使用中根本达不到承诺速度?公共测速网站的结果总让人将信将疑——它们可能被ISP特殊优化,或是受限于服务器位置。更…...
