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

news2024/11/20 13:16:30

资料来源:掘金课程 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.coloradmin.cn/o/1539693.html

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

相关文章

3、Jenkins持续集成-Jenkins安装和插件管理

文章目录 一、Jenkins安装1. 安装JDK2. 获取jenkins安装包3. 安装包上传到服务器&#xff0c;进行安装4. 修改Jenkins配置&#xff08;1&#xff09;低版本Jenkins的rpm包&#xff08;2&#xff09;高版本Jenkins的rpm包 5. 启动Jenkins6. 打开浏览器访问7. 获取并输入admin账户…

Linux文件 profile、bashrc、bash_profile区别

Linux系统中&#xff0c;有三种文件 出现的非常频繁&#xff0c;那就是 profile、bash_profile、bashrc 文件。 1、profile 作用 profile&#xff0c;路径&#xff1a;/etc/profile&#xff0c;用于设置系统级的环境变量和启动程序&#xff0c;在这个文件下配置会对所有用户…

pytest运行结果解析及其改造

简介&#xff1a;场景假设 - 当运行pytest完成后&#xff0c;需要针对运行的结果进行即时的反馈&#xff0c;打印 PASS 或者 FAIL&#xff0c;及其运行失败的原因&#xff0c;最后将结果推送给消息机器人。 历史攻略&#xff1a; pytestallure安装和使用 pytest&#xff1a;…

AcWing 1250. 格子游戏 (并查集,坐标变换)

记录此题的目的&#xff1a; 明确二维的坐标可以映射到一维&#xff1a;在x和y都是从0开始的前提下&#xff0c;假如图形列数为n&#xff0c;(x,y)映射到一维可以写成x * n y。并查集并不好存储二维数据&#xff0c;如果遇到二维数据可以将其映射到一维。 Alice和Bob玩了一个…

git 上传文件夹至远端仓库的方法

上传的远端git可以是gitlab、github、gitee、gitblit或者gitCode等等 以下以GitHub为例说明&#xff1a; 1、登录GitHub网站&#xff08;账户/密码&#xff09; 2、创建一个新的空白项目&#xff08;或者已有的项目&#xff09;hello-world 分支是master &#xff0c;这里默认即…

el-tab 如何点击不同标签触发不同函数

介绍 el-tab本身的功能是点击之后切换不同页&#xff0c;但是我希望点击不同标签就触发不同页 代码实现 <template><el-tabsv-model"activeName"type"card"class"demo-tabs"tab-click"handleClick"><el-tab-pane lab…

PTA L2-028 秀恩爱分得快

古人云&#xff1a;秀恩爱&#xff0c;分得快。 互联网上每天都有大量人发布大量照片&#xff0c;我们通过分析这些照片&#xff0c;可以分析人与人之间的亲密度。如果一张照片上出现了 K 个人&#xff0c;这些人两两间的亲密度就被定义为 1/K。任意两个人如果同时出现在若干张…

dash 初体验(拔草)

Dash简介 Dash 是一个高效简洁的 Python 框架&#xff0c;建立在 Flask、Poltly.js 以及 React.js 的基础上&#xff0c;设计之初是为了帮助前端知识匮乏的数据分析人员&#xff0c;以纯 Python 编程的方式快速开发出交互式的数据可视化 web 应用。 搭建环境 在学习 Dash 的…

深入理解Netty以及为什么项目中要使用?(七)Netty中ByteBuf详解

在Netty中&#xff0c;还有另外一个比较常见的对象ByteBuf&#xff0c;它其实等同于Java Nio中的ByteBuffer&#xff0c;但是ByteBuf对Nio中的ByteBuffer的功能做了很作增强&#xff0c;下面我们来简单了解一下ByteBuf。 下面这段代码演示了ByteBuf的创建以及内容的打印&#…

#Idea打包诺依 多模块项目遇到的问题

##诺依框架中遇到的问题 1. 打包部署出错 Please refer to /Users/zhang/code/giteeProjects/wms-ruoyi/ruoyi-generator/target/surefire-reports for the individual test results. Please refer to dump files (if any exist) [date].dump, [date]-jvmRun[N].dump and [dat…

【技术栈】Redis 删除策略

SueWakeup 个人主页&#xff1a;SueWakeup 系列专栏&#xff1a;学习技术栈 个性签名&#xff1a;保留赤子之心也许是种幸运吧 本文封面由 凯楠&#x1f4f8; 友情提供 目录 相关传送门 前言 1. 删除策略的目标 2. 数据删除策略 2.1 定时删除 2.2 惰性删除 2.3 定期删除…

智能财务新选择!Zoho Books入选福布斯榜单,助力中小企业!

放眼全球&#xff0c;中小企业始终是经济发展的重要组成部分。然而&#xff0c;由于中小企业的规模、流程规范和资源等方面受限较多&#xff0c;从而导致其在管理及运营上存在着诸多问题。其中包括财务管理不规范、成本控制不到位、运营效率低下等&#xff0c;这些问题则直接影…

freeRTOS动态内存heap4源码分析

1 前言 随着功能安全的推广&#xff0c;动态内存分配在RTOS领域的用武之地将越来越小。但heap4毕竟是为RTOS量身打造&#xff0c;相对简单&#xff0c;作为堆内存管理的入门学习&#xff0c;仍是很不错的选择。 1.1 标准c库动态内存函数的弊端 对于标准C库的malloc和free函数&…

2024年【安全员-A证】免费试题及安全员-A证作业考试题库

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年【安全员-A证】免费试题及安全员-A证作业考试题库&#xff0c;包含安全员-A证免费试题答案和解析及安全员-A证作业考试题库练习。安全生产模拟考试一点通结合国家安全员-A证考试最新大纲及安全员-A证考试真题汇…

动态规划——斐波那契问题(Java)

目录 什么是动态规划&#xff1f; 练习 练习1&#xff1a;斐波那契数 练习2&#xff1a;三步问题 练习3&#xff1a;使用最小花费爬楼梯 练习4&#xff1a;解码方法 什么是动态规划&#xff1f; 动态规划&#xff08;Dynamic Programming&#xff0c;DP&#xff09;&…

8个国产全能型AI写作神器,给个标题就能自动生成全文 #其他#知识分享

国外ChatGPT爆火&#xff0c;AI写作在国内也引起不小的瞩目&#xff0c;目前国内的AI写作工具少说也有几十上百个&#xff0c;要在这么多AI写作中找出适合自己的工具&#xff0c;一个一个尝试是不太现实的&#xff0c;所以今天就给大家推荐一些款AI写作工具。帮助你少走弯路&am…

递归和递推的区别

目录 1、递推 2、递归 3、结言 递归 递推 1、递推 递推就是说从初值出发后一直运算到所需的结果。 ——从已知到未知。&#xff08;从小到大&#xff09; 举一个简单的例子&#xff1a; 每天能学习一个小时的编程&#xff0c;那么一个月之后可以学到三十小时的编程知识。…

SSL加密:保护数据传输的安全盾牌

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

尝试Docker Dev Environments

无法从本地目录创建容器环境 创建的容器环境无法在VS Code打开 从官方仓库打开 结果vscode报错。fine&#xff0c;告辞。老老实实用本地环境开发。

2024公认口碑最好的洗地机有哪些?若看重清洁力,这四款最值得买

每当我们要清洁卫生时&#xff0c;是否总是感到腰酸背痛、疲劳不堪&#xff0c;甚至头昏眼花&#xff1f;地板是家中的重要门面&#xff0c;不容忽视的卫生焦点。如今&#xff0c;我们终于多了一位家务打扫的救星——家用洗地地机。一次操作&#xff0c;即可完成扫地除尘、地除…