| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> JavaScript知识库 -> 浅谈react——状态更新之后发生了什么 -> 正文阅读 |
|
[JavaScript知识库]浅谈react——状态更新之后发生了什么 |
学习react底层的过程,和学习其他原理一样,抓住一些关健的点,也就是关键的函数(往往代表了一些阶段),可以对源码的把握以及 图像化的流程更加清晰和易懂。 ? 我们知道facebook团队在react16之后就对react底层有一些重大的重构,一句大白话来解释就是,让react可以实现异步可中断的更新。 ?至于怎么实现的,是react引入了Scheduler调度器,会分配给js线程一个初始的执行时间,源码里面yieldInterval=5ms,如果预留的时间不够浏览器渲染的话,那么react就会将控制权交给浏览器,等到下一帧再进行渲染。具体原理本篇不细讲。 但是在更新过程不是所有阶段都是异步的,这会造成一些预料之外的 回到状态更新上来,可以改变状态大概有这些:
很显然,react开发着肯定会想到接入一套状态更新体制当中,怎么实现呢?答案就是,每次创建一个保存状态更新的对象,在render阶段根据该对象来计算新的state。然而在render阶段,是从rootFiber开始遍历,产生update对象的可能是fiber树中的一个节点,所有得有一种方式能够让react从节点回到顶点。这个关键的过程就是markUpdateLaneFromFiberToRoot函数。
这就是源码,可以看出它不仅接收一个fiber还接收一个优先级,这也对应着update不仅仅有一维的状态保存,还有优先级。逻辑上来看,就是不断通过return指针来往上一层查找,直到找到最上层。 现在拥有了rootfiber立即更新的话肯定有个问题,那就是更新都是同步的,但是react是异步的,那就得调度更新了。核心的函数就是ensureRootIsScheduled。 核心代码如下:
?这样就让render阶段成为一个既可以异步也可以同步也有优先级的了,接下来可以光明正大的更新了。? Render阶段? ? ?? 注意,render阶段的开始于performSyncWorkOnRoot或performConcurrentWorkOnRoot方法的调用。
?注意到异步更新里面有一个关键的函数shouldYield(),这个函数会监控浏览器当前帧是否有剩余时间,没有的话就是终止更新,等到下一帧继续执行。 workInProgress代表正在构建的fiber树,而performUnitOfWork会创建下一个fiber节点并连接上之前的节点,形成fiber树。 在整个render阶段,大概也可以分为两个重要的时期,对应fiber被遍历两遍的过程。第一个阶段就是beginWork()从rootFiber节点开始,向下进行遍历。当遍历到叶子节点时,就会执行completeWork()函数,从叶子节点一步一步到rootfiber。 下面来详细说一下beginWork,这个函数作用很简单,就是根据传入的fiber节点,创建子fiber节点以及通过对比新旧节点给节点打上一些effectTag标签,给commit阶段处理。总体上时分为两个阶段: 1.mount时,因为此时的current树还是空的,所有值会根据不同的fiber.tag来生成对应的fiber节点。
2.update时情况就会比较复杂,会在这个过程这个过程中通过调用reconcileChildren方法来进行diff(不深究),尝试最大可能的复用当前节点。?
实际上,mount和update时都会调用reconcileChildren。但是为什么要调用两个不同的函数呢? 因为mount阶段fiber节点都是首次创建,如果都打上effectTag标签的话,会影响性能。因此,mount的时候,mountchildFibers只会给rootFiber节点打上一个placement的标记,一次性加入。 接下来就是completeWork(current,workInprogress)阶段,这个阶段的作用总结来说就是,mount时 ?根据fiber创建DOM节点,并且初始化DOM节点的props。update阶段就是处理diff之后的收尾工作,依据打完effectTag标签的fiber,形成一个需要更新的props组成的一个数组。 mount阶段和update阶段重要的函数:
mount阶段也会初始化DOM节点上的props。
其中updatePayload就是需要更新的props数组。 最后在completeWork阶段还有一个小细节,为了让commit阶段更加快速的操作DOM,在completeWork这个回宿阶段会格外的生成一个effectList数组,里面包含的是下一阶段需要更新的DOM。 至此,render阶段才算完成,这个过程可能不是一帆风顺的,会由于高优先级以及浏览器的渲染时间而终止以及恢复。现在currentFiber还没有变化,真正改变DOM的就是下一阶段,也是同步的,commit阶段。 commit的入口函数是commitRoot(root) 这个阶段大致分为三个阶段:
1.befor mutation阶段。 主要就是遍历effectList,以及调用主函数commitBeforeMutationEffects,这个函数里面会调用getSnapshotBeforeUpdate这个钩子开始,这也看出来是在同步阶段执行,并且在componentDidMount之前执行。其次就是调度useEffect,这是个重头戏。
?这里额外聊一下,useEffect的调度过程,useEffect在这个阶段是通过flushPassiveEffects调度useEffect,但是不会立马执行,因为这是一个异步的过程,flushPassiveEffects依赖的参数rootWithPendingPassiveEffects此时为null,只有等到layout阶段,rootWithPendingPassiveEffects才会被赋值为effectList,执行回调,这也是useEffect异步回调的原理。 而且因为这个原因,useEffect的执行要比?componentDidMount、componentDidUpdate 执行要靠后,而且视图已经更新,不会阻塞页面渲染。 2.mutation阶段。 真正执行DOM操作的过程。这个阶段也是遍历effectList,主函数是commitMutationEffects。接收一个fiberRoot和更新级作为参数。
?下面来聊聊具体的effectTag,Placement tag调用了commitPlacement函数,获取父fiber节点,再进行insertBefore或着appendChild的DOM操作插入DOM节点。而当fiber中包含Update effectTag会调用commitUpdate,执行函数组件的useLayoutEffect的销毁函数,对于原生DOM组件,会将在completeWork阶段附着在fiber节点上的updateQuene对应的内容渲染到页面上。至于Deletion effectTag,就是删除fiber节点以及解绑ref,执行compoentWillUnmount。并且会执行useEffect的销毁函数。 3.layout阶段。 和之前的阶段一样,这个阶段也是会遍历effectList。这个阶段的DOM结构已经更新渲染完成。这个阶段的主函数就是commitLayoutEffects。主要的逻辑代码如下:
?1.commitLayoutEffectOnFiber(之前的版本叫commitLifeCycles),对于类组件,会根据current==null来判断执行componentDidmount以及componentDidupdate函数。而且此时setState的第二个参数回调函数会在此执行。对于函数组件,他会调用useLayoutEffect的回调函数以及调度useEffect的销毁和回调函数。注意一点,此时useEffect并没有执行,等到layout阶段之后再执行。 2.commitAttachRef,这个函数做的事情比较简单,获取实例DOM实例,然后再更新ref,如果ref是个回到函数,也是会执行,是对象实例则直接赋值。 至此整个流程差不多结束,但是内存里面的两颗fiber树是啥时候切换的呢? 答案是:在mutation和layout阶段之间。 总结一下从状态更新,到页面显示的全过程:
这只是从底层原理和流程的角度去看整个更新过程,react在这个给过程中也有许多的地方值得深入学习,比如采用位运算以及位标识,用一个数组的奇偶数来保存相反的回调函数...不过应该也算是讲明白了一些流程。学习路上进无止境,一起加油! 如果有不对的地方,烦请读者指出,共同进步!!! |
|
JavaScript知识库 最新文章 |
ES6的相关知识点 |
react 函数式组件 & react其他一些总结 |
Vue基础超详细 |
前端JS也可以连点成线(Vue中运用 AntVG6) |
Vue事件处理的基本使用 |
Vue后台项目的记录 (一) |
前后端分离vue跨域,devServer配置proxy代理 |
TypeScript |
初识vuex |
vue项目安装包指令收集 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/23 17:07:28- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |