React系列 之 React进阶 含源码解读 (一)事件合成、state原理

资料来源:掘金课程 https://juejin.cn/book/6945998773818490884?enter_from=course_center&utm_source=course_center

记录一些笔记

事件合成

React的事件其实是React重新实现的一套事件系统。目标是统一管理事件,提供一种跨浏览器一致性的事件处理方式。
元素绑定事件并不是原生事件,而是React合成的事件,所谓的“合成”,是指你用React添加的一个事件,在真正的dom元素中,可能是1对多的,比如为<input>绑定一个onChange事件,是由blur, change, focus等多个事件合成的。最后事件对象经过不同的事件插件处理后,统一绑定到顶层容器上,这个顶层容器,V17之前是document,V17是app容器。

State( Legacy模式下的state)

在不同的React模式下,state的更新流程是不同的

React的模式包括:

  • legacy模式:平时使用比较多的模式
  • blocking模式:可以视为concurrent的优雅降级版本和过渡版本
  • concurrent模式:V18

1 类组件中的state

类组件中的setState()方法来更新state

setState(obj, callback)
  • 第一个参数:
    (1)obj为一个对象,就是即将合并的state
    (2)obj是一个函数,function(state,props){ return {/* 合并新的state*/}}
  • 第二个参数:
    state更新后的副作用函数,可以获取当前setState更新后的最新state值,做一些操作
/* 第一个参数为function类型 */
this.setState((state,props)=>{return { number:1 } 
})
/* 第一个参数为object类型 */
this.setState({ number:1 },()=>{console.log(this.state.number) //获取最新的number
})

限制state更新视图的方式:

  • pureComponent 可以对 state 和 props 进行浅比较,如果没有发生变化,那么组件不更新
  • shouldComponentUpdate 生命周期可以通过判断前后 state 变化来决定组件需不需要更新,需要更新返回true,否则返回false。

之前说过,类组件的setState实际调用的是Updater对象上的enqueueSetState方法,所以想知道底层是如何运行的可以看一下精简版源码

// react-reconciler/src/ReactFiberClassComponent.js
enqueueSetState(){/* 每一次调用`setState`,react 都会创建一个 update 里面保存了 */const update = createUpdate(expirationTime, suspenseConfig);/* callback 可以理解为 setState 回调函数,第二个参数 */callback && (update.callback = callback) /* enqueueUpdate 把当前的update 传入当前fiber,待更新队列中 */enqueueUpdate(fiber, update); /* 开始调度更新 */scheduleUpdateOnFiber(fiber, expirationTime);
}

所以每个fiber对象的更新,会放到对应的fiber对象的一个待更新队列中,最后开启调度更新,进入到React底层 做的这些事?

  1. setState产生当前更新的优先级
  2. 从fiber Root根部 fiber 向下调和子节点,对比发生更新的地方,找到发生更新的组件
  3. 在这些组件中合并state,然后触发render函数,得到新的UI试图层,完成render阶段
  4. 到commit阶段:替换真实的DOM。
  5. 仍在commit阶段,执行setState中的callback函数

第3步中提到了“合并state”,这与批量更新有关,批量更新batchUpdate则与事件系统息息相关。React采用事件合成的形式

/* 源码 react-dom/src/events/DOMLegacyEventPluginSystem.js */
/* 在`legacy`模式下,所有的事件都将经过此函数同一处理 */
function dispatchEventForLegacyPluginEventSystem(){/** !!! 下面来重点看这个批量事件更新函数*   handleTopLevel 事件处理函数*/batchedEventUpdates(handleTopLevel, bookKeeping); // 
}
/* 源码 react-dom/src/events/ReactDOMUpdateBatching.js */
function batchedEventUpdates(fn,a){/* 开启批量更新  */isBatchingEventUpdates = true;try {/* 这里执行了的事件处理函数, 比如在一次点击事件中触发setState,那么它将在这个函数内执行 */return batchedEventUpdatesImpl(fn, a, b);} finally {/* try 里面 return 不会影响 finally 执行  *//* 完成一次事件批量更新, 关闭开关  */isBatchingEventUpdates = false;}
}

举个例子,下面组件中,点击一次<button>,调用了三次setState

export default class index extends React.Component{state = { number:0 }handleClick= () => {// 下面的三次setState传入的newStateObj,会被合并this.setState({ number:this.state.number + 1 },()=>{   console.log( 'callback1', this.state.number)  })console.log(this.state.number)this.setState({ number:this.state.number + 1 },()=>{   console.log( 'callback2', this.state.number)  })console.log(this.state.number)this.setState({ number:this.state.number + 1 },()=>{   console.log( 'callback3', this.state.number)  })console.log(this.state.number)// 控制台输出:// 0, 0, 0, callback1 1 ,callback2 1 ,callback3 1}render(){return <div>{ this.state.number }<button onClick={ this.handleClick }  >number++</button></div>}
} 

在整个React上下文执行栈中会变成这样
在这里插入图片描述
批量更新的规则可以被打破吗?我如果不想让他合并呢?那就可以使用异步操作,比如promisesetTimeout

// 比如 handleClick 这么写
handleClick = (){setTimeout(()=>{this.setState({ number:this.state.number + 1 },()=>{   console.log( 'callback1', this.state.number)  })console.log(this.state.number)this.setState({ number:this.state.number + 1 },()=>{    console.log( 'callback2', this.state.number)  })console.log(this.state.number)this.setState({ number:this.state.number + 1 },()=>{   console.log( 'callback3', this.state.number)  })console.log(this.state.number)// 控制台输出:callback1 1 , 1, callback2 2 , 2,callback3 3 , 3})
}

在React上下文执行栈中会变成:
在这里插入图片描述
但如果我也在异步环境下,也使用批量更新的模式,应该怎么做呢?
可以使用ReactDOM的批量更新方法unstable_bachedUpdates, 手动批量更新

handleClick = (){setTimeout(()=>{unstable_bachedUpdates(() => {this.setState({ number:this.state.number + 1 },()=>{   console.log( 'callback1', this.state.number)  })console.log(this.state.number)this.setState({ number:this.state.number + 1 },()=>{    console.log( 'callback2', this.state.number)  })console.log(this.state.number)this.setState({ number:this.state.number + 1 },()=>{   console.log( 'callback3', this.state.number)  })console.log(this.state.number)// 控制台输出:0 , 0 , 0 , callback1 1 , callback2 1 ,callback3 1})})
}

如果要改变优先级,可以使用flushSync,React同一级别更新优先级关系是:
flushSync中的setState > 正常执行上下文中的 setState > setTimtout/Promise中的 setState

flushSync补充:flushSync在同步条件下,会合并正常执行上下文中的setState,因此下面的2
被合并了

handerClick=()=>{setTimeout(()=>{this.setState({ number: 1  })})this.setState({ number: 2  })ReactDOM.flushSync(()=>{this.setState({ number: 3  })})this.setState({ number: 4  })// 输出: 3 4 1
}
render(){console.log(this.state.number)return ...
}

2 函数组件中的state

  [ ①state , ②dispatch ] = useState(③initData)

initDate参数:

  • state初始值
  • 或函数:返回值作为state初始值

dispatch函数的入参:

  • 直接传入newState的值
  • 或函数:(旧state)=>(/*新state*/) 返回值作为newState值

注意:

  • 当调用改变 state 的函数dispatch,在本次函数执行上下文中,是获取不到最新的 state 值的。原因:函数组件更新就是函数的执行,在函数一次执行过程中,函数内部所有变量重新声明,所以只有在下一次函数组件执行时,state才会被更新为新的值。所以在同一个函数执行上下文中,state还是原来的值。
    • 那应该如何监听state变化?
      A:在函数组件中,一般使用useEffect监听state的变化
  • 在useState的dispatchAction中,不要使用相同的state(地址相同的state),需要浅拷贝一份state作为newState的值。因为在dispatchAction的处理逻辑中,会对state进行浅比较,如果两次state指向相同的内存空间,会默认state相等,就不会发生视图更新了。所以一般使用dispatchState({...state})。因为...会浅拷贝一份

比较类组件的setState和函数组件的useState的异同点

相同点:

  • 都更新了视图,底层都调用了scheduleUpdateOnFiber方法,而且事件驱动情况下都有批量更新规则。
    不同点:
  • 在不是pureComponent组件模式下,setState不会浅比较两次state的值,只有调用setState,就会执行更新。但是useState中的dispatch 会默认比较两次state是否相同,然后决定是否更新组件。
  • setState有callback;但是函数组件中,智能通过useEffect来执行state变化引起的副作用。
  • setState在底层逻辑上主要是和老state进行合并处理,而useState更倾向于重新赋值。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

5G智能网关助力工业铸造设备监测升级

随着物联网技术的迅猛发展和工业4.0浪潮的推进&#xff0c;传统工业正面临着严峻的转型升级压力。在这一背景下&#xff0c;铸造行业——这一典型的传统重工业领域&#xff0c;也必须积极探索借助5G、物联网、边缘计算等技术提升生产经营效率的新路径。 本文就基于佰马合作伙伴…

【技巧】ChatGPT Prompt 提示语大全

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhang.cn] 主要来自&#xff1a;https://github.com/f/awesome-chatgpt-prompts ChatGPT SEO提示 Contributed by: StoryChief AI Reference: 7 Powerful ChatGPT Prompts to Create SEO Content Faster 供稿人&#xff1a;…

链表oj测试题(上)

链表的申明&#xff1a; struct ListNode {int val;struct ListNode* next; }; 1.题1 删除指定元素 例如&#xff1a;链表1 2 6 3 4 5 6&#xff0c;然后选择删除元素6&#xff0c;返回的链表为1 2 3 4 5 。 代码演示&#xff1a; typedef struct ListNode ListNode;List…

Spark与flink计算引擎工作原理

Spark是大批量分布式计算引擎框架&#xff0c;scale语言开发的&#xff0c;核心技术是弹性分布式数据集&#xff08;RDD&#xff09;可以快速在内存中对数据集进行多次迭代&#xff0c;支持复杂的数据挖掘算法及图形计算算法&#xff0c;spark与Hadoop区别主要是spark多个作业之…

什么是行业垂直类媒体?有哪些?怎么邀约

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体胡老师。 行业垂直类媒体是聚焦于特定行业或领域的媒体平台。 行业垂直类媒体不同于主流媒体&#xff0c;它们专注于提供与某个特定领域相关的深入内容和服务&#xff0c;例如商业新闻、旅游、数字…

能拍英语的搜题软件?九个免费好用的大学生搜题工具 #经验分享#知识分享#其他

积极参加社团活动和实践项目&#xff0c;可以帮助大学生拓宽人脉圈和锻炼实际操作能力。 1.粉鹿搜题 这是一个公众号 搜题拥有非常强大的题库&#xff0c;包含IT认证、建筑工程:、会计资格、教师资格、研究生、公务员等类型的题目。 下方附上一些测试的试题及答案 1、BPR基…

Qt 利用共享内存实现一次只能启动一个程序(单实例运行)

Qt 利用共享内存实现一次只能启动一个程序 文章目录 Qt 利用共享内存实现一次只能启动一个程序摘要利用共享内存实现一次只能启动一个程序示例代码 关键字&#xff1a; Qt、 unique、 单一、 QSharedMemory、 共享内存 摘要 今天接着在公司搞我的屎山代码&#xff0c;按照…

[MAUI]集成高德地图组件至.NET MAUI Blazor项目

文章目录 前期准备&#xff1a;注册高德开发者并创建 key登录控制台创建 key获取 key 和密钥 创建项目创建JS API Loader配置权限创建定义创建模型创建地图组件创建交互逻辑 项目地址 地图组件在手机App中常用地理相关业务&#xff0c;如查看线下门店&#xff0c;设置导航&…

LeetCode 热题 100 | 堆(二)

目录 1 什么是优先队列 1.1 优先队列与堆的关系 1.2 如何定义优先队列 1.3 如何使用优先队列 1.4 如何设置排序规则 2 347. 前 K 个高频元素 2.1 第 2 步的具体实现 2.2 举例说明 2.3 完整代码 3 215. 数组中的第 K 个最大元素 - v2 菜鸟做题&#xff0c;语…

Shell学习

一、 变量 shell是弱类型语言&#xff0c;不用定义数据类型&#xff0c;默认都是字符串。 变量与值之间不得有空格 只能包含数字、字母、下划线 不能以数字开头 区分大小写 根据变量的作用域可以将shell变量分为&#xff1a;全局变量、局部变量、环境变量。全局变量通常和…

app自动化测试怎么学?

app测试的主要内容有那些 1、功能测试 : 查看功能是否正常&#xff0c;主要针对每一个功能点进行一一测试&#xff0c;主要核心就是把验证的每个测试点都满足需求的对应功能&#xff0c;验证标准就是让预期结果和实际结果保持一致。 2、安装卸载测试&#xff1a;首先要测试的…

【Linux】从零认识进程 — 中下篇

送给大家一句话&#xff1a; 人一切的痛苦&#xff0c;本质上都是对自己无能的愤怒。而自律&#xff0c;恰恰是解决人生痛苦的根本途径。—— 王小波 从零认识进程 1 进程优先级1.1 什么是优先级1.2 为什么要有优先级1.3 Linux优先级的特点 && 查看方式1.4 其他概念 2…

深度解析深度学习中的长短期记忆网络(LSTM)(含代码实现)

在深度学习中&#xff0c;长短期记忆网络&#xff08;LSTM&#xff09;是一种强大的循环神经网络结构&#xff0c;能够更好地处理长序列数据并减轻梯度消失的问题。本文将介绍LSTM的工作原理&#xff0c;并使用PyTorch实现一个简单的LSTM模型来展示其在自然语言处理中的应用。 …

MongoDB完全开发手册(一篇学会MongoDB所有知识点)

目录 一、MongoDB 基础 1.1 、MongoDB 是什么&#xff1f; 1.2、 MongoDB 的存储结构是什么&#xff1f; 1.3 、文档 1.4 、集合 1.5 、数据库 1.6、 MongoDB 有什么特点&#xff1f; 1.7、 MongoDB 适合什么应用场景&#xff1f; 二、MongoDB 存储引擎 2.1 、MongoDB…

Autosar Crypto Interface学习笔记

文章目录 前言Functional specificationError classificationError detection API specificationType DefinitionsFunction definitionsGeneral APICryIf_InitCryIf_GetVersionInfo Job Processing InterfaceCryIf_ProcessJobDispatch Key IDs匹配KeyId Job Cancellation Inter…

【嵌入式——QT】Charts常见的图表的绘制

【嵌入式——QT】Charts常见的图表的绘制 柱状图QBarSetQBarSeriesQBarCategoryAxis图示 饼图堆叠柱状图百分比柱状图散点图和光滑曲线图代码示例 柱状图 QBarSet 用于创建柱状图的数据集。 主要函数 setLabel()&#xff1a;设置数据集标签 &#xff1b;setLabelBrush()&am…

租用阿里云2核2G服务器配置报价,61元和99元

阿里云2核2G服务器配置优惠价格61元和99元&#xff0c;61元是轻量应用服务器2核2G3M带宽、50G高效云盘&#xff0c;99元服务器是ECS云服务器经济型e实例2核2G、3M固定带宽、40G ESSD entry 系统盘。活动 aliyunfuwuqi.com/go/aliyun 阿里云服务器网aliyunfuwuqi.com根据上面的官…

​ YOLOv9改进策略:SPPELAN优化 | 新一代高效可形变卷积DCNv4如何做二次创新?高效结合SPPELAN| CVPR2024

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文改进内容&#xff1a; DCNv4来自CVPR2024 的论文&#xff0c;它不仅收敛速度明显快于DCNv3&#xff0c;而且正向速度提高了3倍以上。这一改进使DCNv4能够充分利用其稀疏特性&#xff0c;成为最快的通用核心视觉算子之一。 |新一代…

如何减少pdf的文件大小?pdf压缩工具介绍

文件发不出去&#xff0c;有时就会耽误工作进度&#xff0c;文件太大无法发送&#xff0c;这应该是大家在发送PDF时&#xff0c;常常会碰到的问题吧&#xff0c;那么PDF文档压缩大小怎么做呢&#xff1f;因此我们需要对pdf压缩后再发送&#xff0c;那么有没有好用的pdf压缩工具…

牛客题霸-SQL进阶篇(刷题记录二)

本文基于前段时间学习总结的 MySQL 相关的查询语法&#xff0c;在牛客网找了相应的 MySQL 题目进行练习&#xff0c;以便加强对于 MySQL 查询语法的理解和应用。 由于涉及到的数据库表较多&#xff0c;因此本文不再展示&#xff0c;只提供 MySQL 代码与示例输出。 部分题目因…