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更倾向于重新赋值。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【LabVIEW FPGA入门】FPGA 存储器(Memory)

可以使用内存项将数据存储在FPGA块内存中。内存项以2kb为倍数引用FPGA目标上的块内存。每个内存项引用一个单独的地址或地址块&#xff0c;您可以使用内存项访问FPGA上的所有可用内存。如果需要随机访问存储的数据&#xff0c;请使用内存项。 内存项不消耗FPGA上的逻辑资源&…

Unity 中 苹果眼镜开发入口

1. 文档介绍了Unity对Apple新操作系统visionOS的支持。 2. Unity提供了完善的文档、模板和支持,帮助开发者快速为visionOS开发应用。 3. Unity的跨平台框架AR Foundation和XR Interaction Toolkit可以帮助现有移动和XR应用无缝迁移到visionOS。 4. 在visionOS上,可以利用Uni…

Linux:Jenkins全自动持续集成持续部署(3)

在上一章部署好了之后&#xff0c;还需要点击一下才能进行部署&#xff0c;本章的效果是&#xff1a;当gitlab上的代码发生了变化后&#xff0c;我们不需要做任何事情不需要去点击构建按钮&#xff0c;Jenkins直接自动检测变化&#xff0c;然后自动去集成部署Linux&#xff1a;…

vue 修改element-plus主题色

一、安装SCSS npm install sass --save-dev npm install sass-loader --save-dev npm install node-sass --save-dev npm install vue-style-loader --sava-dev 二、添加主题文件theme.scss forward "element-plus/theme-chalk/src/common/var.scss" with ($col…

autodl数据集下载及裁剪子图像操作

一、autodl数据集下载 里面数据集可以直接使用 一般是先用无卡模式对数据集进行解压&#xff0c;然后移动文件夹到指定位置 1.解压操作如下 unzip 压缩包路径 -d 解压文件夹 2.移动操作如下 mv 移动前文件夹 移动后文件夹 3.移动文件夹内的文件 以下命令将当前目录下名为sourc…

RabbitMq高可用

消息队列高级 服务异步通信-高级篇1.消息可靠性1.1.生产者消息确认1.2.消息持久化1.3.消费者消息确认1.4.消费失败重试机制1.5.总结 2.死信交换机2.1.初识死信交换机2.2.TTL2.3.延迟队列 3.惰性队列3.1.消息堆积问题3.2.惰性队列 4.MQ集群4.1.集群分类4.2.普通集群4.3.镜像集群…

矩阵计算-线性系统和 LU 分解

一、三角系统 …… 二、高斯消元法 …… 三、LU分解--直接三角分解法 求解线性方程Axb&#xff1a; 参考视频&#xff1a;【数值分析】矩阵LU三角分解| 速成讲解 考试宝典_哔哩哔哩_bilibili 令ALU&#xff0c;其中L是单位下三角矩阵&#xff08;对角线上元素都是1&#xff…

【漏洞复现】netgear路由器 boarddataww 存在RCE漏洞

免责声明&#xff1a;文章来源互联网收集整理&#xff0c;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;所产生的一切不良后果与文章作者无关。该…

PAT题解 --- 寻宝图

今天是PTA题库解法讲解的第十天&#xff0c;今天我们要讲解浪漫侧影&#xff0c;题目如下&#xff1a; 题解思路&#xff1a; 要解决这个问题&#xff0c;可以使用深度优先搜索&#xff08;DFS&#xff09;方法来遍历每一个陆地或宝藏格子&#xff0c;标记所有与之相连的格子…

【JavaScript 漫游】【042】表单和FormData 对象

文章简介 本篇文章为【JavaScript 漫游】专栏的第 042 篇文章&#xff0c;对浏览器模型中的表单和 FormData 对象的知识点进行了总结。 表单概述 表单&#xff08;<form>&#xff09;用来收集用户提交的数据&#xff0c;发送到服务器。比如&#xff0c;用户提交用户名…

面试算法-87-分隔链表

题目 给你一个链表的头节点 head 和一个特定值 x &#xff0c;请你对链表进行分隔&#xff0c;使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。 你应当 保留 两个分区中每个节点的初始相对位置。 示例 1&#xff1a; 输入&#xff1a;head [1,4,3,2,5,2], x …

2024年 前端JavaScript Web APIs 第五天 笔记

5.1-BOM和延迟函数setTimeout 5.2-事件循环eventloop 1-》 3 -》2 1-》 3 -》2 5.3-location对象 案例&#xff1a;5秒钟之后自动跳转页面 <body><a href"http://www.itcast.cn">支付成功<span>5</span>秒钟之后跳转到首页</a><sc…

利用API打造卓越的用户体验

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;日常聊聊 ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 正文 1. 数据驱动的设计 2. 功能扩展与整合 3. 实时性与响应性 4. 个性化推荐与定制化服务 结语 我的其他博客 正文 随着数字化时代的…

程序设计语言+嵌入式系统设计师备考笔记

0、前言 本专栏为个人备考软考嵌入式系统设计师的复习笔记&#xff0c;未经本人许可&#xff0c;请勿转载&#xff0c;如发现本笔记内容的错误还望各位不吝赐教&#xff08;笔记内容可能有误怕产生错误引导&#xff09;。 1、嵌入式系统开发与设计 1.1嵌入式应用程序的生成与加…

机器学习基础知识面经(个人记录)

朴素贝叶斯 特征为理想状态下的独立同分布&#xff0c;作为机器学习的重要基石和工具 由贝叶斯公式推导而来 是后验概率&#xff1a;在B发生的条件下A发生的概率。 是似然概率: 在 发生的条件下 发生的概率。 是先验概率: 发生的概率&#xff0c;而不考虑 的影响。 是…

Redis入门到实战-第五弹

Redis实战热身Hashes篇 完整命令参考官网 官网地址 声明: 由于操作系统, 版本更新等原因, 文章所列内容不一定100%复现, 还要以官方信息为准 https://redis.io/Redis概述 Redis是一个开源的&#xff08;采用BSD许可证&#xff09;&#xff0c;用作数据库、缓存、消息代理和…

【Java初阶(四)】数组的定义和使用

❣博主主页: 33的博客❣ ▶文章专栏分类: Java从入门到精通◀ &#x1f69a;我的代码仓库: 33的代码仓库&#x1f69a; 目录 1.前言2.数组的概念2.1数组的初始化2.2数组的使用2.2.1数组元素访问2.2.2遍历数组 3.数组是引用类型3.1实例3.2 认识null 4.数组的应用4.1 二分查找4.2…

合成孔径雷达干涉测量InSAR数据处理、地形三维重建、形变信息提取、监测

原文链接&#xff1a;合成孔径雷达干涉测量InSAR数据处理、地形三维重建、形变信息提取、监测https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247598798&idx7&snc054ed7c9d9c433d00837a7798080935&chksmfa820329cdf58a3f6b5986d6d4da3d19f81e3efd0b159f…

YOLOv9/YOLOv8算法改进【NO.106】使用YOLOv7下采样进行改进

前 言 YOLO算法改进系列出到这&#xff0c;很多朋友问改进如何选择是最佳的&#xff0c;下面我就根据个人多年的写作发文章以及指导发文章的经验来看&#xff0c;按照优先顺序进行排序讲解YOLO算法改进方法的顺序选择。具体有需求的同学可以私信我沟通&#xff1a; 首推…

米多论文怎么用 #学习方法#职场发展

米多论文是一款专为论文写作者设计的工具&#xff0c;可以帮助用户进行论文的查重和降重。它的使用非常简单&#xff0c;只需将需要检测的论文内容粘贴到相应的输入框中&#xff0c;点击“检测”按钮即可开始查重。米多论文通过比对用户提交的论文和互联网上已经存在的内容&…