react源码-debuger解析- createRoot阶段1
Posted lin-fighting
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了react源码-debuger解析- createRoot阶段1相关的知识,希望对你有一定的参考价值。
看懂这个之后https://react.iamkasong.com开始debugger源码
CreateRoot
ReactDOM.createRoot(document.getElementById(''))
craeteRoot主要调用了createContainer
创建rootFiber和FiberRoot,并且将它们联系起来。
createContainer```调用了```createFiberRoot
const root: FiberRoot = new FiberRootNode(...) //FiberRoot
const uninitializedFiber = createHostRootFiber(...) //rootFiber
// 连接rootFiber和fiberRottNode
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;
initializeUpdateQueue(uninitializedFiber);// 初始化rootFiber的updateQueue
最后createRoot
return new ReactDOMRoot(root);
render方法就在ReactDOMRoot的原型上。
所以craeteRoot做的事情就是:
- 创建rootFiber和FiberRoot,将他们连接起来,并且初始化rootFiber的updateQueue
- 返回一个ReactDOMRoot的实例.`
render
ReactDOM.createRoot(xx).render(<App/>)
render里面主要调用了updateContainer
方法(updateContiner属于Reconciler模块的)
updateContainer(children, root, null, null);
updateConinter:
1 创建update对象; 2 update对象插入fiber.UpdateQueue; 3 scheduleUpdateOnFiber开启调度;
- 创建update对象(createUpdate)
// 创建第一次的update
const update = createUpdate(eventTime, lane);
// update.payload为需要挂载在根节点的组件
//对应hostComponent的update的paylad就是Render的第一个参数,createRoot().render(xx)的xx
update.payload = element; // <App/>
- 将生成的update对象插入updateQueue(enqueueUpdate)
// 将生成的update加入updateQueue
enqueueUpdate(current, update, lane);
- 开启第一次调度更新(scheduleUpdateOnFiber)
/ 开始第一次调度更新, 找到rootfiber => render阶段 => commit阶段
const root = scheduleUpdateOnFiber(current, lane, eventTime);
createUpdate
// 创建update对象
export function createUpdate(eventTime: number, lane: Lane): Update<*>
const update: Update<*> =
eventTime, // 任务时间,通过performance.now()获取的毫秒数
lane, //优先级
tag: UpdateState, // 更新的类型,包括UpdateState | ReplaceState | ForceUpdate | CaptureUpdate
// 对于ClassComponent,payload为this.setState的第一个传参。对于HostRoot,payload为ReactDOM.render的第一个传参。
payload: null, //更新挂在的数据
callback: null, // 更新的回调函数,比如ReactDOM.render(xx,xx,()=>),或是this.setState的第二个参数。
next: null, //与其他update连成指针
;
return update;
enqueueUpdate
通过update的next指针,形成环状链表,挂在fiber.UpdateQueue.shard.pending上。
//update顺序:
u1=>u2=>u3
//
fiber.UpdateQueue.shard.pending = u3=>u2=>u1=>u3....
ScheduleUpdateOnFiber
- 通过带有update的fiber往上查找,找到rootFiber。 markUpdateLaneFormFIberToRoot
- 通过找到的rootFiber,开始调度更新, ensureRootIsScheduled(root, eventTime)
ensureRootIsScheduled
-
获取当前任务的优先级,判断有没有任务,没有的话重置root.callbackNode,并且退出。有的话往下判断
-
判断是否有正在工作的任务,有的话比对一下两者优先级,如果一样,表示正在工作的任务优先级也很高,直接返回null,让当前正在工作的任务继续工作。如果当前任务优先级较高,就会中断正在工作的任务,优先调度当前任务。
-
根据当前的优先级判断是执行scheduleCallback注册performSyncWorkOnRoot还是performConcurrentWorkOnRoot。(同步or异步)
-
更高级的任务task对象会被赋值到root.callbackNode上。
performConcurrentWorkOnRoot(root,didTimout)
-
schedule会将工作的fiber和是否过期作为参数传入,然后通过didTImeout等参数,判断是否过期,执行renderRootConcurrent或者是renderRootSync函数
-
这个函数会被作为注册进scheduleCallback的方法,那么它必须满足scheduleCallback的规定,每次执行完毕后都需要有一个返回值。
-
并且这个函数每次执行完毕后,都会调用
ensureRootIsScheduled(root, now());
,就是重新判断是否有更高级的任务,通过上面我们知道如果有更高级的任务,那么会被赋值到root.callbackNode上。当ensureRootIsScheduled
调用完毕后,会判断const originalCallbackNode = root.callbackNode; //现在调度的任务 task对象 .... ..... // 每次执行完一次任务之后,需要继续调度,查看是否有更高优先级的 ensureRootIsScheduled(root, now()); // 如果有优先级更高的任务,root.callbackNode !== originalCallbackNode if (root.callbackNode === originalCallbackNode) // 只是因为档期桢不够时间,还是原本的work,继续返回给Schedule调度 return performConcurrentWorkOnRoot.bind(null, root); return null
如果经过调用
ensureRootIsScheduled
后,四种情况-
root.callbackNode还是等于原来的originalCallbackNode,表示没有新的任务需要调度,所以直接返回当前的任务作为下一个scheduleCallback调度的任务。
-
如果有新的任务,但是新的任务跟当前的任务优先级相关,通过上面
ensureRootIsScheduled
的介绍也知道,如果任务优先级一样,那么直接return null,而root.callbackNode也不会被改变。 -
有新的任务,并且优先级更高,那么
ensureRootIsScheduled
会调用通scheduleCallback注册performSyncWorkOnRoot或者performConcurrentWorkOnRoot函数,返回新的task对象,赋值root.callbackNode,那么等ensureRootIsScheduled
执行完毕后,就不满足root.callbackNode === originalCallbackNode,所以直接返回null,那么scheduleCallback如果收到一个null,就会将当前的任务取消掉,准备执行优先级更高的任务了。 -
没有新的任务,并且当前任务执行完毕。
ensureRootIsScheduled
判断到没有任务,将root.callbackNode置为Null,那么也不满足条件,所以performConcurrentWokrOnRoot会返回null,该任务会在scheduleCallback被取消。
-
renderRootConcurrent
- 顾名思义i,异步执行root。
- 先将react hooks对象全部置为抛错的对象。 pushDIspatcher()
- 首先第一次进来,会根据root.current也就是rootFiber创建workInprogress fiber树,调用
prepareFreshStack
调用createWorkInprogress
workInporgress.stateNode = current.stateNode // workInprogress也跟FIberRoot有关联。
workInprogress.alternate = current
current.alternate = workInprogress
-
workInprogress fiber通过alternate属性跟current fiber树关联起来。react使用双缓存机制进行更新的。FiberRoot,有且只有一个,但是rootFiber有很多个,比如第一个workInprogress就是另一个rootFiber。
-
调用workLoopConcurrent,准备进入render阶段。
-
workLoopConcurrent执行完毕之后,会判断当前任务是否执行完毕,是的话清除全局变量,比如workInProgressRoot = null;返回退出的状态给```performConcurrentWOrkOnRoot的``的exitStatus使用。
workLoopConcurrent
function workLoopConcurrent()
// Perform work until Scheduler asks us to yield
// 判断当前桢浏览器还有没有时间执行js
while (workInProgress !== null && !shouldYield())
performUnitOfWork(workInProgress);
*shouldYield是schedule提供的,用来判断当前帧是否有剩余时间,如果有就执行performUnitOfWork,若没有,就退出循环。继续走renderRootConcurrent接下来的步骤。等待scheduleCallback继续执行performConcurrentWorkOnRoot调度。
这样,createRoot到render阶段的流程就走完了。
render阶段-commit阶段
render阶段开始于performConcurrentWorkOnRoot/perfromSyncWorkOnRoot;
- 我们知道performConcurrentWorkOnRoot调用renderRootConcurrent,renderRootConcurrent调用workLoopConcurrent执行任务,而renderRootConcurrent在执行完workLoopCOncurrent之后,会判断当前是否还有任务没有render。返回一个状态给exitStatus。
- 而如果任务执行都执行了render阶段之后,就会调用
finishConcurrentRender
,判断exitStatus的值,调用commitRoot(root),正式开启commit阶段。 - 从这里就可以看出,render阶段,是可以中断的,它通过不断地调用注册performConcurrentWornOnRoot,去执行render阶段,每当浏览器帧时间不够就暂停退出,等到下一帧继续执行performConcurrentWornOnRoot。
- 而只有当全部的fiber执行完render阶段之后,才会调用commitRoot执行commit阶段。commit阶段主要是对dom的操作,而这一步是必须同步的。这里先了解。
render阶段
render阶段开始于performConcurrentWorkOnRoot/perfromSyncWorkOnRoot。最终都会调用performUnitOfWork,去执行每一个fiber。
function workLoopConcurrent()
// Perform work until Scheduler asks us to yield
// 判断当前桢浏览器还有没有时间执行js
while (workInProgress !== null && !shouldYield())
performUnitOfWork(workInProgress);
第一个开始工作的是刚才创建的workInprogress。
performUnitOfWork
- 通过unitOfWork.alterant判断当前处于mount阶段还是update阶段(rootFiber是唯一一个在mount的时候有alterant)
- 调用beginWork执行该fiber的递阶段。
beginWOrk(current, workInprogress, renderLanes)
- 对完成递阶段的fiber,执行completeUnitOfWork。
- 对于还有子孙节点的fiber,将workInprogress设置为子fiber节点,退出。等待下一帧执行,继续调度workInprogress的beginWork阶段。
beginWork
-
对于一开始进入的rootFIber的workInprogress,他有altername
-
对于rootFiber,根据workInProgress.tag判断,调用
updateHostRoot
updateHostRoot
- cloneUpdateQueue(current, workInprogress),将current fiber上的updateQueue复制到了workInprogress.updateQueue
- 调用
processUpdateQueue(workInprogress, nextProps,null, renderLanes)
消费updateQueue.shard.pending上所有的update环状链表。
-
而其他fiber,进入的时候unitOfWork.alternate为null,表示处于mount阶段。
processUpdateQueue
processUpdateQueue是一个非常重要的方法
- update对象分为两种,函数组件一种,放在fiber上的hooks对象上,fiber.memoizedState存放hooks对象的链表,而函数的Update存放在hooks对象上的hooks.queue.shard.pending上
- 类组件和hostRootComponent公用的一种,他们的update都是放在fiber.updateQueue.shard.pendign上,用环状链表存储这,
- 而processUpdateQueue,就是用来消费类组件这一类的update的。
执行逻辑:
-
将updateQueue上的firstBaseUpdate和lastBaseUpdate取出,将shard.pending上的update环状链表剪开,插入到lastBaseUpdate上。
firstBaseUpdate: u1, lastBaseUpdate: u2 shard.pending: u5 -> u4 -> u3 -> u5 所以这一次的update顺序: u1 -> u2 -> u3 -> u4 -> u5
-
为了防止update丢失,会将当前,存放到current fiber.update.lastBaseUpdate上
-
接着开始遍历Update链表,执行update。
update优先级
-
判断update优先级,如果当前update优先级较低,就会跳过,重新插入到一条新的链表中。
if (newLastBaseUpdate === null) newFirstBaseUpdate = newLastBaseUpdate = clone; newBaseState = newState; //有update需要跳过,那么下次fiber.baseState需要停留在此次更新前的state。即这 else newLastBaseUpdate = newLastBaseUpdate.next = clone;
保存在newFirstBaseUpdate和newLastBaseUpdate上,前者永远指向第一个update,后者永远指向最后一个Update。
-
如果有跳过的update,那么此时fiber的state应该停留在跳过的update时候的状态,而不应该只计算优先级高的几个update后就拿来当新的state。
-
所以
// 计算最新的state let newState = queue.baseState // 赋值当前fiber的状态的变量 let newBaseState = null;
newState是用来计算每个Update执行之后的状态,newBaseState是所有的update执行过后,赋值到fiber上的state的状态。一旦有Update跳过,newState和newBaseState就不一样。如上,当发现一个
if (!isSubsetOfLanes(renderLanes, updateLane)) // 优先级比较低的,不执行。以链表的形式存储到新的firstBaseUpdate上, const clone: Update<State> = eventTime: updateEventTime, lane: updateLane, tag: update.tag, payload: update.payload, callback: update.callback, next: null, ; if (newLastBaseUpdate === null) newFirstBaseUpdate = newLastBaseUpdate = clone; newBaseState = newState; //有update需要跳过,那么下次fiber.baseState需要停留在此次更新前的state。即这 else newLastBaseUpdate = newLastBaseUpdate.next = clone; // Update the remaining priority in the queue. newLanes = mergeLanes(newLanes, updateLane);
update优先级比较低,并且他是当前第一个跳过的update,就会执行
newBaseState = newState
,而第二个跳过的update不会执行,所以newBaseState的状态永远的停留在了当前跳过的update的状态。 -
如果优先级高的,那么不会被跳过,但是为了保证状态的连续性,如果有跳过的Update,那么当前执行的update即使优先级很高,也要预留一份继续插入lastBaseUpdate上
if (newLastBaseUpdate !== null) // 如果已经有update被逃过,当前update也必须保存在链表后面 const clone: Update<State> = eventTime: updateEventTime, // This update is going to be committed so we never want uncommit // it. Using NoLane works because 0 is a subset of all bitmasks, so // this will never be skipped by the check above. lane: NoLane, tag: update.tag, payload: update.payload, callback: update.callback, next: null, ; newLastBaseUpdate = newLastBaseUpdate.next = clone;
如,当执行高优先级的update的时候,会判断是否有跳过的update,有就会clone一份,放入lastBaseUpdate后面。
小总结:为了保证优先级update被跳过后状态的连续性,做了什么处理?
- 1 首先是,workInprogress fiber上的update链表,会保存一份在current fiber.updateQUeue上
- 2 有跳过的update,Fiber的state跟遍历update之后计算的state不同,永远停留在跳过update时候的状态
- 3 有跳过的update,那么他之后所有的update,即使优先级高被执行了,但还是会保留一份,插在lastBaseUpdate后面
执行优先级高的update
…
以上是关于react源码-debuger解析- createRoot阶段1的主要内容,如果未能解决你的问题,请参考以下文章