react源码解析3.react源码架构
视频学习(高效学习):视频
课程目录:
1.开篇介绍和面试题
2.react的设计理念
3.react源码架构
4.源码目录构造和调试
5.jsx&外围api
6.legacy和concurrent模式入口函数
7.Fiber架构
8.render阶段
9.diff算法
10.commit阶段
11.生命周期
12.状态更新流程
13.hooks源码
14.手写hooks
15.scheduler&Lane
16.concurrent模式
17.context
18事件零碎
19.手写迷你版react
20.总结&第一章的面试题解答
21.demo
这一章的目标是让咱们认识一下react源码架构和各个模块。
在真正的代码学习之前,咱们须要在大脑中有一个react源码的地图,晓得react渲染的大抵流程和框架,这样能力从上帝视角看react是怎么更新的,来吧少年。
react的外围能够用ui=fn(state)来示意,更具体能够用
const state = reconcile(update);const UI = commit(state);
下面的fn能够分为如下一个局部:
- Scheduler(调度器): 排序优先级,让优先级高的工作先进行reconcile
- Reconciler(协调器): 找出哪些节点产生了扭转,并打上不同的Flags(旧版本react叫Tag)
- Renderer(渲染器): 将Reconciler中打好标签的节点渲染到视图上
一图胜千言:
jsx
jsx是js语言的扩大,react通过babel词法解析(具体怎么转换能够查阅babel相干插件),将jsx转换成React.createElement,React.createElement办法返回virtual-dom对象(内存中用来形容dom阶段的对象),所有jsx实质上就是React.createElement的语法糖,它能申明式的编写咱们想要组件呈现出什么样的ui成果。在第5章jsx咱们会具体介绍jsx解析之后的后果。
Fiber双缓存
Fiber对象下面保留了包含这个节点的属性、类型、dom等,Fiber通过child、sibling、return(指向父节点)来造成Fiber树,还保留了更新状态时用于计算state的updateQueue,updateQueue是一种链表构造,下面可能存在多个未计算的update,update也是一种数据结构,下面蕴含了更新的数据、优先级等,除了这些之外,下面还有和副作用无关的信息。
双缓存是指存在两颗Fiber树,current Fiber树形容了以后出现的dom树,workInProgress Fiber是正在更新的Fiber树,这两颗Fiber树都是在内存中运行的,在workInProgress Fiber构建实现之后会将它作为current Fiber利用到dom上
在mount时(首次渲染),会依据jsx对象(Class Component或的render函数者Function Component的返回值),构建Fiber对象,造成Fiber树,而后这颗Fiber树会作为current Fiber利用到实在dom上,在update(状态更新时如setState)的时候,会依据状态变更后的jsx对象和current Fiber做比照造成新的workInProgress Fiber,而后workInProgress Fiber切换成current Fiber利用到实在dom就达到了更新的目标,而这一切都是在内存中产生的,从而缩小了对dom好性能的操作。
例如上面代码的Fiber双缓存构造如下,在第7章会具体解说
function App() { const [count, setCount] = useState(0); return ( <> <h1 onClick={() => { // debugger; setCount(() => count + 1); }} > <p title={count}>{count}</p> xiaochen </h1> </> )}ReactDOM.render(<App />, document.getElementById("root"));
scheduler
Scheduler的作用是调度工作,react15没有Scheduler这部分,所以所有工作没有优先级,也不能中断,只能同步执行。
咱们晓得了要实现异步可中断的更新,须要浏览器指定一个工夫,如果没有工夫残余了就须要暂停工作,requestIdleCallback貌似是个不错的抉择,然而它存在兼容和触发不稳固的起因,react17中采纳MessageChannel来实现。
//ReactFiberWorkLoop.old.jsfunction workLoopConcurrent() { while (workInProgress !== null && !shouldYield()) {//shouldYield判断是否暂停工作 workInProgress = performUnitOfWork(workInProgress); }}
在Scheduler中的每的每个工作的优先级应用过期工夫示意的,如果一个工作的过期工夫离当初很近,阐明它马上就要过期了,优先级很高,如果过期工夫很长,那它的优先级就低,没有过期的工作寄存在timerQueue中,过期的工作寄存在taskQueue中,timerQueue和timerQueue都是小顶堆,所以peek取出来的都是离当初工夫最近也就是优先级最高的那个工作,而后优先执行它。
Lane模型
react之前的版本用expirationTime
属性代表优先级,该优先级和IO不能很好的搭配工作(io的优先级高于cpu的优先级),当初有了更加细粒度的优先级示意办法Lane,Lane用二进制位示意优先级,二进制中的1示意地位,同一个二进制数能够有多个雷同优先级的位,这就能够示意‘批’的概念,而且二进制不便计算。
这好较量车较量,在较量开始的时候会调配一个赛道,较量开始之后大家都会抢内圈的赛道(react中就是抢优先级高的Lane),较量的序幕,最初一名赛车如果落后了很多,它也会跑到内圈的赛道,最初达到目的地(对应react中就是饥饿问题,低优先级的工作如果被高优先级的工作始终打断,到了它的过期工夫,它也会变成高优先级)
Lane的二进制位如下,1的bits越多,优先级越低
//ReactFiberLane.jsexport const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000;export const NoLane: Lane = /* */ 0b0000000000000000000000000000000;export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001;export const SyncBatchedLane: Lane = /* */ 0b0000000000000000000000000000010;export const InputDiscreteHydrationLane: Lane = /* */ 0b0000000000000000000000000000100;const InputDiscreteLanes: Lanes = /* */ 0b0000000000000000000000000011000;const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000100000;const InputContinuousLanes: Lanes = /* */ 0b0000000000000000000000011000000;export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000100000000;export const DefaultLanes: Lanes = /* */ 0b0000000000000000000111000000000;const TransitionHydrationLane: Lane = /* */ 0b0000000000000000001000000000000;const TransitionLanes: Lanes = /* */ 0b0000000001111111110000000000000;const RetryLanes: Lanes = /* */ 0b0000011110000000000000000000000;export const SomeRetryLane: Lanes = /* */ 0b0000010000000000000000000000000;export const SelectiveHydrationLane: Lane = /* */ 0b0000100000000000000000000000000;const NonIdleLanes = /* */ 0b0000111111111111111111111111111;export const IdleHydrationLane: Lane = /* */ 0b0001000000000000000000000000000;const IdleLanes: Lanes = /* */ 0b0110000000000000000000000000000;export const OffscreenLane: Lane = /* */ 0b1000000000000000000000000000000;
reconciler (render phase)
Reconciler产生在render阶段,render阶段会别离为节点执行beginWork和completeWork(前面会讲),或者计算state,比照节点的差别,为节点赋值相应的effectFlags(对应dom节点的增删改)
协调器是在render阶段工作的,简略一句话概括就是Reconciler会创立或者更新Fiber节点。在mount的时候会依据jsx生成Fiber对象,在update的时候会依据最新的state造成的jsx对象和current Fiber树比照构建workInProgress Fiber树,这个比照的过程就是diff算法。
diff算法产生在render阶段的reconcileChildFibers函数中,diff算法分为单节点的diff和多节点的diff(例如一个节点中蕴含多个子节点就属于多节点的diff),单节点会依据节点的key和type,props等来判断节点是复用还是间接新创建节点,多节点diff会波及节点的增删和节点地位的变动,具体见第9章。
reconcile时会在这些Fiber上打上Flags标签,在commit阶段把这些标签利用到实在dom上,这些标签代表节点的增删改,如
//ReactFiberFlags.jsexport const Placement = /* */ 0b0000000000010;export const Update = /* */ 0b0000000000100;export const PlacementAndUpdate = /* */ 0b0000000000110;export const Deletion = /* */ 0b0000000001000;
render阶段遍历Fiber树相似dfs的过程,‘捕捉’阶段产生在beginWork函数中,该函数做的次要工作是创立Fiber节点,计算state和diff算法,‘冒泡’阶段产生在completeWork中,该函数次要是做一些收尾工作,例如解决节点的props、和造成一条effectList的链表,该链表是被标记了更新的节点造成的链表
深度优先遍历过程如下,图中的数字是程序,return指向父节点,第9章具体解说
function App() { return ( <> <h1> <p>count</p> xiaochen </h1> </> )}
看如下代码
function App() { const [count, setCount] = useState(0); return ( <> <h1 onClick={() => { setCount(() => count + 1); }} > <p title={count}>{count}</p> xiaochen </h1> </> )}
如果p和h1节点更新了则effectList如下,从rootFiber->h1->p,,顺便说下fiberRoot是整个我的项目的根节点,只存在一个,rootFiber是利用的根节点,可能存在多个,例如多个ReactDOM.render(<App />, document.getElementById("root"));
创立多个利用节点
renderer(commit phase)
Renderer产生在commit阶段,commit阶段遍历effectList执行对应的dom操作或局部生命周期。
Renderer是在commit阶段工作的,commit阶段会遍历render阶段造成的effectList,并执行实在dom节点的操作和一些生命周期,不同平台对应的Renderer不同,例如浏览器对应的就是react-dom。
commit阶段产生在commitRoot函数中,该函数次要遍历effectList,别离用三个函数来解决effectList上的节点,这三个函数是commitBeforeMutationEffects、commitMutationEffects、commitLayoutEffects,他们次要做的事件如下,前面会具体解说,当初在大脑里有一个构造就行
concurrent
它是一类性能的合集(如fiber、schduler、lane、suspense),其目标是为了进步利用的响应速度,使利用cpu密集型的更新不在那么卡顿,其外围是实现了一套异步可中断、带优先级的更新。
咱们晓得个别浏览器的fps是60Hz,也就是每16.6ms会刷新一次,而js执行线程和GUI也就是浏览器的绘制是互斥的,因为js能够操作dom,影响最初出现的后果,所以如果js执行的工夫过长,会导致浏览器没工夫绘制dom,造成卡顿。react17会在每一帧调配一个工夫(工夫片)给js执行,如果在这个工夫内js还没执行完,那就要暂停它的执行,等下一帧继续执行,把执行权交回给浏览器去绘制。
比照下开启和未开启concurrent mode的区别,开启之后,构建Fiber的工作的执行不会始终处于阻塞状态,而是分成了一个个的task
未开启concurrent
开启concurrent