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.hqwc.cn/news/563071.html

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

相关文章

怎么拆解台式电脑风扇CPU风扇的拆卸步骤-怎么挑

今天我就跟大家分享一下如何选购电脑风扇的知识。 我也会解释一下机箱散热风扇一般用多少转。 如果它恰好解决了您现在面临的问题&#xff0c;请不要忘记关注本站并立即开始&#xff01; 文章目录列表&#xff1a;大家一般机箱散热风扇都用多少转&#xff1f; 机箱散热风扇选择…

AbstractQueuedSynchronizer 独占式源码阅读

概述 ● 一个int成员变量 state 表示同步状态 ● 通过内置的FIFO队列来完成资源获取线程的排队工作 属性 AbstractQueuedSynchronizer属性 /*** 同步队列的头节点 */private transient volatile Node head;/*** 同步队列尾节点&#xff0c;enq 加入*/private transient …

专业矢量绘图设计软件:Sketch for mac 中文激活版

Sketch for Mac 是一款专业的矢量图形设计工具&#xff0c;主要用于 UI/UX 设计、网页设计、图标设计等领域。它的界面简洁、易用&#xff0c;功能强大&#xff0c;可以帮助设计师快速创建高质量的设计作品。 人性化界面 Sketch的界面非常简洁。最顶端的工具箱包含了最重要的操…

Oracle Data Guard部署

Oracle的主备DG搭建 1. 修改主机名,同步时间 主库IP&#xff1a;192.168.100.137 备库IP&#xff1a;192.168.100.138配置主机名(主库) Hostname zygjpdb vim /etc/hosts 192.168.100.137 zygjpdb 192.168.100.138 zygjsdbvim /etc/sysconfig/network HOSTNAMEzygjpdb ------…

【项目自我反思之vue的组件通信】

为什么子组件不能通过props实时接收父组件修改后动态变化的值 一、现象二、可能的原因1.响应式系统的限制2.异步更新队列3.父组件和子组件的生命周期4.子组件内部对 props 的处理 三、组件通信的几种场景&#xff08;解决方案&#xff09;1.子组件想修改父组件的数据2.子组件传…

win10开启了hyper-v,docker 启动还是报错 docker desktop windows hypervisor is not present

问题 在安装了docker windows版本后启动 docker报错docker desktop windows hypervisor is not present 解决措施 首先确认windows功能是否打开Hyper-v 勾选后重启&#xff0c;再次启动 启动后仍报这个错误&#xff0c;是Hyper-v没有设置成功 使用cmd禁用再启用 一.禁用h…

RuleApp资源社区,知识付费社区,可对接typecho的小程序APP

强大的文章/社区/自媒体客户端&#xff0c;支持打包为安卓&#xff0c;苹果&#xff0c;小程序。包括文章模块&#xff0c;用户模块&#xff0c;支付模块&#xff0c;聊天模块&#xff0c;商城模块等基础功能&#xff0c;包含VIP会员&#xff0c;付费阅读等收费体系&#xff0c…

计算机网络——26通用转发和SDN

通用转发和SDN 网络层功能&#xff1a; 转发&#xff1a; 对于从某个端口 到来的分组转发到合适的 输出端口路由&#xff1a; 决定分组从源端 到目标端的路径 网络层 传统路由器的功能 每个路由器(Per Route)的控制平面 &#xff08;传统&#xff09; 每个路由器上都有实…

创建vue3项目并集成cesium插件运行

创建vue3项目并集成cesium插件 一、vue项目创建 1、前期准备 node.js&npm或yarn 本地开发环境已经安装好。 参考安装 2、安装vue-cli&#xff0c;要求3以上版本 #先查看是否已经安装 vue -V#安装 npm install -g vue/cli4.5.17 示例&#xff1a;Idea工具 页面 Termin…

CSK6 接入聆思平台(LSPlatform)

一、开发环境 硬件&#xff1a;视觉语音大模型AI开发套件 二、使用大语言模型 官方指导文档&#xff1a; 开始使用 | 聆思文档中心 获取API密钥 | 聆思文档中心 1、注册 提交申请之后需要将注册电话号码通过微信发送给聆思科技工作人员&#xff0c;工作人员授权后&#xff…

章节10实验--Ubuntu18.04 Qt MySQL libqsqlmysql.so

前言: 内容参考《操作系统实践-基于Linux应用与内核编程》一书的示例代码和教材内容&#xff0c;所做的读书笔记。本文记录再这里按照书中示例做一遍代码编程实践加深对操作系统的理解。 引用: 《操作系统实践-基于Linux应用与内核编程》 作者&#xff1a;房胜、李旭健、黄…

【2024第十二届“泰迪杯”数据挖掘挑战赛】B题基于多模态特征融合的图像文本检索—解题全流程(持续更新)

2024 年(第 12 届)“泰迪杯”数据挖掘挑战赛B题 解题全流程&#xff08;持续更新&#xff09; -----基于多模态特征融合的图像文本检索 一、写在前面&#xff1a; ​ 本题的全部资料打包为“全家桶”&#xff0c; “全家桶”包含&#xff1a;数据、代码、模型、结果csv、教程…