继上一篇react源码学习(1)
创建FiberRoot和rootFiber
//render调用方法
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>, // mount是null
children: ReactNodeList, // ReactElement
container: Container, //容器
forceHydrate: boolean,
callback: ?Function, //回叼
) {
const maybeRoot = container._reactRootContainer;
let root: FiberRoot;
if (!maybeRoot) {
// Initial mount //初始化mount
root = legacyCreateRootFromDOMContainer(
container,
children,
parentComponent,
callback,
forceHydrate,
);
} else {
// 第二次mount的时候
root = maybeRoot;
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(root);
originalCallback.call(instance);
};
}
// Update
updateContainer(children, root, parentComponent, callback);
}
return getPublicRootInstance(root);
}
- legacyRenderSubtreeIntoContainer是render最后调用的方法,他会判断当前容器是否有挂载属性,有的话就直接服用老的FiberRoot,然后调用updateContainer方法开启调度,比如wbepack热更新。
- 对于第一次,会调用legacyCreateRootFromDOMContainer方法,创建FiberRoot
function legacyCreateRootFromDOMContainer(
container: Container,
initialChildren: ReactNodeList,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
isHydrationContainer: boolean,
): FiberRoot {
if (isHydrationContainer) {
} else {
// First clear any existing content.
let rootSibling;
// 创建FiberRoot
const root = createContainer(
container,
LegacyRoot,
null, // hydrationCallbacks
false, // isStrictMode
false, // concurrentUpdatesByDefaultOverride,
'', // identifierPrefix
noopOnRecoverableError, // onRecoverableError
null, // transitionCallbacks
);
// 容器上挂载root
container._reactRootContainer = root;
// Initial mount should not be batched.
// 初始化渲染优先级比较高,不应该不应分批处理。
flushSync(() => {
// 进入调度阶段了
updateContainer(initialChildren, root, parentComponent, callback);
});
return root;
}
}
可以看到这个方法就是创建FiberRoot然后挂载到容器上,最后调用flushSync调用updateContainer调度。flushSync表示以同步到方式执行updateContainer。因为初始化渲染优先级比较高。
那么createContainer就是用来创建FiberRoot和rootFiber的
createConinter会调用createFiberRoot
export function createFiberRoot(
containerInfo: any,
tag: RootTag,
hydrate: boolean,
initialChildren: ReactNodeList,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
isStrictMode: boolean,
concurrentUpdatesByDefaultOverride: null | boolean,
identifierPrefix: string,
onRecoverableError: null | ((error: mixed) => void),
transitionCallbacks: null | TransitionTracingCallbacks,
): FiberRoot {
// FiberRoot
const root: FiberRoot = (new FiberRootNode(
containerInfo,
tag,
hydrate,
identifierPrefix,
onRecoverableError,
): any);
// RootFiber
const uninitializedFiber = createHostRootFiber(
tag,
isStrictMode,
concurrentUpdatesByDefaultOverride,
);
//FiberRoot和rooFiber联系
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;
const initialState: RootState = {
element: initialChildren,
isDehydrated: hydrate,
cache: (null: any), // not enabled yet
transitions: null,
};
// rootFiber的初始化state
uninitializedFiber.memoizedState = initialState;
// 初始化RootFiber的updateQueue
initializeUpdateQueue(uninitializedFiber);
return root;
}
createFiberRoot主要做了
- 1 创建FIberRoot和rootFiber并且联系起来
- 2 初始化rootFiber的state
- 3 初始化rootFiber的updateQueue
创建任务对象并存储于任务队列
上面说到updateContainer会开启调度。
它会创建一个update任务,插入到rootFiber上,并且开启调度。
export function updateContainer(
element: ReactNodeList, // <App/>
container: OpaqueRoot, // FiberRoot
parentComponent: ?React$Component<any, any>, // null
callback: ?Function, ){
// current就是RootFiber
const current = container.current;
// 获取当前react应用初始化的时间
const eventTime = requestEventTime();
// 获取优先级
const lane = requestUpdateLane(current);
// 创建update
const update = createUpdate(eventTime, lane);
// 对于HostRoot,update的payload就是element
update.payload = {element};
// 将update插入fiber.updateQueue上
enqueueUpdate(current, update, lane);
// 开启调度
const root = scheduleUpdateOnFiber(current, lane, eventTime);
if (root !== null) {
entangleTransitions(root, current, lane);
}
return lane;
}
- 创建update
- 插入到fiber.updateQueue.shard.pending上
- 调用scheduleUpdateOnFiber开启调度
执行任务前的准备工作
上面已经创建了update,放入了任务队列之中,现在应该来执行任务了。updateConitnaer调用了schedlueUpdateOnFiber来开启调度。
这个方法
// 创建Update之后,就需要开启调度更新了。
// 做的事情:
// 1: 通过markUpdateLaneFromFiberToRoot找到rootFiber
// 2: 找到rootFiber之后,调用ensureRootIsScheduled开始调度
export function scheduleUpdateOnFiber(
fiber: Fiber,
lane: Lane,
eventTime: number
){
/**
* 判断是否hi无限循环的update,如果是就报错。
* 比如在componentWillUpdate或者componentDidupdate生命周期中重复调用setState方法,就会发生这种情况。
* react限制了嵌套更新的数量防止无限制更新,限制的嵌套数量是50
*/
checkForNestedUpdates();
// 遍历找到rootFiber
const root = markUpdateLaneFromFiberToRoot(fiber, lane);
if (root === null) {
return null;
}
// 开始调度
ensureRootIsScheduled(root, eventTime);
}
主要做了两件事情
- 通过fiber找到rootFiber
- 调用ensureRootIsScheduled开始调度
ensureRootIsScheduled
顾名思义,确保root正在调度。
ensureRootIsScheduled这个方法是关键。他是react自己实现的一个优先级的调度函数。
如
- 判断当前调度任务的优先级以及是否有正在调度的任务,有就判断两者优先级,优先级相同,则不做处理,优先级不同,打断当前的调度,开启新的调度,优先执行优先级高的任务
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
//正在工作的任务
const existingCallbackNode = root.callbackNode;
//当前调度的任务的优先级
const nextLanes = getNextLanes(
root,
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes
);
// 如果当前调度的任务优先级是NoLanes,不需要调度,直接刷新全局变量,并且取消当前的工作的任务
if (nextLanes === NoLanes) {
// Special case: There's nothing to work on.
if (existingCallbackNode !== null) {
cancelCallback(existingCallbackNode);
}
root.callbackNode = null;
root.callbackPriority = NoLane;
return;
}
// 获取此次任务的Priority
const newCallbackPriority = getHighestPriorityLane(nextLanes);
// 获取当前正在执行的任务的优先级
const existingCallbackPriority = root.callbackPriority;
if ( existingCallbackPriority === newCallbackPriority){
return;
}
//如果不一样,并且存在正在工作的任务,取消当前正在工作的任务
if (existingCallbackNode != null) {
// Cancel the existing callback. We'll schedule a new one below.
cancelCallback(existingCallbackNode);
}
}
- 第二则是通过判断当前调度任务的优先级,同步的话调用performSyncWorkOnRoot,异步的话调用performConcurrentWorkOnRoot。
// 调度一个新的任务
let newCallbackNode;
// 判断当前调度的任务是同步还是异步
if (newCallbackPriority === SyncLane) {
scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));
}else {
// 异步调度,scheduleCallback的返回值就是当前注册的任务newTask
newCallbackNode = scheduleCallback(
schedulerPriorityLevel,
performConcurrentWorkOnRoot.bind(null, root)
);
}
root.callbackPriority = newCallbackPriority;
root.callbackNode = newCallbackNode;
走到这里表示了当前调度的任务是最高优先级的,所以,他会把当前最新的任务重新挂载在root上,注意,这个跟另一个函数performConcurrentWorkOnRoot配合,实现react自己任务优先级的调度。
- 我们现在只需要了解ensureRootIsSchedule主要就是用来判断当前是否有更高优先级的任务,有的话就停止当前任务,创建新的调度
构建workInprogress Fiber树中的rootFiber
当面说到执行performConcurrentWorkOnRoot函数,他是render阶段的入口。
performConcurrentWorkOnRoot会根据当前任务是否过期,决定调用renderRootConcurrent或者renderRootSync,这两个函数会返回一个状态,performConcurrentWorkOnRoot根据这个状态来决定是否进入commit阶段。还是开启新的一轮调度。
function performConcurrentWorkOnRoot(root, didTimeout) {
let exitStatus = shouldTimeSlice
? renderRootConcurrent(root, lanes)
: renderRootSync(root, lanes);
}
因为performConcurretnWorkOnRoot是通过Scheduler调度的,所以她会接受一个didTimeout参数,表示当前帧是否有剩余时间。
重点看下renderRootConcurrent
renderRootConcurrent会创建workInprogress fiber树的rootFiber,
然后调用workLoopConcurrent真正去执行任务。
function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
// 当wokrInprogress不等于root,就要创建workInprogress
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
// 构建workInporgressFiber 树及rootFiber
prepareFreshStack(root, lanes);
}
do {
try {
// 真正执行任务
workLoopConcurrent();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
}
prepareFreshStack用于创建workInprogress Fiber的rootFiber。
function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
root.finishedWork = null; // finishWork标识render阶段完成后构建待提交的对象
root.finishedLanes = NoLanes; //初始化优先级
if (workInProgress !== null) {
let interruptedWork = workInProgress.return;
while (interruptedWork !== null) {
const current = interruptedWork.alternate;
unwindInterruptedWork(
current,
interruptedWork,
workInProgressRootRenderLanes
);
interruptedWork = interruptedWork.return;
}
}
// 构建workInprogress的FiberRoot
workInProgressRoot = root;
// 构建rootFiber
const rootWorkInProgress = createWorkInProgress(root.current, null);
workInProgress = rootWorkInProgress;
workInProgressRootRenderLanes =
subtreeRenderLanes =
workInProgressRootIncludedLanes =
lanes;
workInProgressRootExitStatus = RootInProgress;
workInProgressRootFatalError = null;
workInProgressRootSkippedLanes = NoLanes;
workInProgressRootInterleavedUpdatedLanes = NoLanes;
workInProgressRootRenderPhaseUpdatedLanes = NoLanes;
workInProgressRootPingedLanes = NoLanes;
workInProgressRootConcurrentErrors = null;
workInProgressRootRecoverableErrors = null;
return rootWorkInProgress;
}
可以看到,调用createWorkInProgress就是来创建workInprogress fiber树的rootFiber。
workLoopConcurrent方法解析
上面说到,workLoopConcurrent是真正执行任务的方法,
function workLoopConcurrent() {
// Perform work until Scheduler asks us to yield
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
- 我们刚刚才创建了workInprogress fiber的rootFiber赋值给workInProgress ,shouldYild是判断当前帧是否还有多余时间让他执行performUnitOfWork。
- react16可以中断的最小粒度就是fiber,每一帧至少会执行一个fiebr的调度。当shouldYield返回true的时候,表示该中断了,得把线权交给浏览器了。
- 然后退出循环。renderRootConcurrent会返回一个退出状态给performConcurrentWorkOnRoot,performConcurrentWorkOnRoot会决定要不要进入commit阶段。
- 如果任务还没结束,performConcurrentWorkOnRoot不会进入commit阶段,反而会继续调用ensureRootIsScheduled方法,就是我们前面说的可以来判断当前是否有更高优先级的任务,如果没有,那么performConcurrentWorkOnRoot就会继续返回当前的任务。而schedulerCallback执行的时候会以当前任务的返回值决定该任务是否需要继续调度,需要的话就下一帧继续执行。
performUnitOfWork解析
上面说到。workLoopConcurrent会调用performUnitOfWork去调度fiber。那么performUnitOfWork是怎么处理fiber的呢?
function performUnitOfWork(unitOfWork: Fiber): void {
const current = unitOfWork.alternate;
let next;
next = beginWork(current, unitOfWork, subtreeRenderLanes);
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// 进入归阶段
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
ReactCurrentOwner.current = null;
}
- 参数就是当前调度的fiber,对于第一次,这就是workInprogress fiber的rootFiber。
- beginWork,递阶段,处理fiber,创建子fiber。从父到子
- 如果next === null,调用completeUnitOfWork,归阶段,从子到父。
- react16通过循环来模拟15的递归处理阶段。beginWork就像是递阶段,completeUnitOfWork就像是归阶段。但他们是可以中断的。
当所有fiber处理完成之后,表示render阶段完成,要进入commit阶段了。
render阶段进入commit阶段。
workLoopConcurrent执行完毕之后,会判断workInProgress是否等于null。如果等于null,表示Reconciler工作完成,要开启commit阶段了。
function renderRootConcurrent(root: FiberRoot, lanes: Lanes){
// 构建workInporgressFiber 树及rootFiber
prepareFreshStack(root, lanes);
do {
try {
// 真正执行任务
workLoopConcurrent();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
// Check if the tree has completed.
// 判断是否render完毕
if (workInProgress !== null){
return RootInProgress;
} else {
// Set this to null to indicate there's no in-progress render.
workInProgressRoot = null;
workInProgressRootRenderLanes = NoLanes;
// Return the final exit status.
return workInProgressRootExitStatus;
}
}
如上,renderRootConcurrent会返回一个状态,RooInprogress就表示当前还在调度之中,而当workInprogress为null,就返回workInProgressRootExitStatus退出的状态。
而我们知道performConcurrentWorkOnRoot会根据renderRootConcurrent返回的状态决定是否进入commit阶段
function performConcurrentWorkOnRoot(root, didTimeout){
let exitStatus = shouldTimeSlice
? renderRootConcurrent(root, lanes)
: renderRootSync(root, lanes);
if (exitStatus !== RootInProgress) {
...
// 将构建好的rootfiber存储到FiberRoot上
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
//完成了render阶段之后,开启commit阶段
finishConcurrentRender(root, exitStatus, lanes);
}
// 每次执行完performConcurrentWorkOnRoot都会调用ensureRootIsScheduled来判断当前是否有更高优先级的任务需要调度
ensureRootIsScheduled(root, now());
//如果没有更高优先级或者当前任务就是最高优先级的,继续返回该任务
if (root.callbackNode === originalCallbackNode) {
// performConcurrentWorkOnRoot是ScheduleCallback注册的函数,而ScheduleCallback执行的时候,需要通过返回来确定该任务是否继续执行
// 这里通过ensureRootIsScheduled调度之后,发现root上面挂载的任务还是当前这个任务,表示当前的任务依然是最高优先级的。
// 所以,需要返回当前的任务给ScheduleCallback,以表示当前任务依然是最高优先级,需要执行。
return performConcurrentWorkOnRoot.bind(null, root);
}
//当调用ensureRootIsScheduled调度之后,如果有更高优先级的,或者任务都执行完毕了,那么这里返回null给scheduleCallback
// 表示当前任务已经结束,当Schedule执行注册的函数performConcurrentWorkOnRoot,结果是Null的时候,他会认为该任务已经结束。
// 会将该任务从最小堆中取出,然后继续调度,看有没有更高优先级的任务,注意,Schedule和React里面有各自的调度系统
return null;
}
如上,performConcurrentWorkOnRoot,在确定退出状态是结束状态的时候,就会调用finishConcurrentRender,顾名思义,已经完成了并发的render工作。
而finishConcuurentRender的工作就是
function finishConcurrentRender(root, exitStatus, lanes) {
switch (exitStatus) {
case RootInProgress:
case RootFatalErrored: {
throw new Error("Root did not complete. This is a bug in React.");
}
....
case RootCompleted: {
// The work completed. Ready to commit.
commitRoot(root, workInProgressRootRecoverableErrors);
break;
}
default: {
throw new Error("Unknown root exit status.");
}
}
}
根据传入的状态,调用commitRoot函数,开启commit阶段,commit阶段是不可中断的。
commitRoot
commitRoot是commit阶段的入口
function commitRoot(root: FiberRoot, recoverableErrors: null | Array<mixed>) {
const previousUpdateLanePriority = getCurrentUpdatePriority(); // 获取当前优先级保存
const prevTransition = ReactCurrentBatchConfig.transition;
try {
ReactCurrentBatchConfig.transition = null;
setCurrentUpdatePriority(DiscreteEventPriority); // 修改当前优先级
// 因为commit阶段是不可被打断的,所以优先级绝对是最高的
commitRootImpl(root, recoverableErrors, previousUpdateLanePriority);
} finally {
ReactCurrentBatchConfig.transition = prevTransition;
setCurrentUpdatePriority(previousUpdateLanePriority); //恢复之前的优先级
}
return null;
}
因为commit阶段是不可被打断的,所以执行commit阶段之前,优先级要设为最高优先级。
commitRootImpl
commitRootImpl是commit阶段主要执行的函数,来看看他做了什么
function commitRootImpl(
root: FiberRoot,
recoverableErrors: null | Array<mixed>,
renderPriorityLevel: EventPriority
) {
// --------before-mutation-之前的阶段-start-------
const finishedWork = root.finishedWork; // rootFiber
const lanes = root.finishedLanes; //优先级
// 重置FiberRoot的属性
root.finishedWork = null;
root.finishedLanes = NoLanes;
// 重置变量
root.callbackNode = null;
root.callbackPriority = NoLane;
//调度useEffect
if (
(finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
(finishedWork.flags & PassiveMask) !== NoFlags
) {
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
pendingPassiveEffectsRemainingLanes = remainingLanes;
scheduleCallback(NormalSchedulerPriority, () => { // 以普通优先级调度useEffect
flushPassiveEffects();
// This render triggered passive effects: release the root cache pool
// *after* passive effects fire to avoid freeing a cache pool that may
// be referenced by a node in the tree (HostRoot, Cache boundary etc)
return null;
});
}
}
// -----------------beforeMutation阶段------------------
const shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(
root,
finishedWork
);
// ------------mutation阶段------------------
// The next phase is the mutation phase, where we mutate the host tree.
commitMutationEffects(root, finishedWork, lanes);
}
// ------------layout阶段----------------
commitLayoutEffects(finishedWork, root, lanes);
// -------------------layout之后--start-------------------
const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;
if (rootDoesHavePassiveEffects) { // 有useEffect的effects
rootDoesHavePassiveEffects = false;
rootWithPendingPassiveEffects = root; // 赋值全局变量
pendingPassiveEffectsLanes = lanes;
}
...
ensureRootIsScheduled(root, now());
// If layout work was scheduled, flush it now.
flushSyncCallbacks();
..
主要做了6件事情
- 2 before-mutation之前的阶段,开始执行dom操作之前,将所有effects执行完毕。 全局变量重置,调度useEffect
- 3 before-mutation阶段,调用commitBeforeMutationEffects 执行dom操作之前
- 4 mutation阶段,调用commitMutationEffects 执行dom操作
- 5 mutation阶段之后,layout阶段之前,切换fiber树,root.current = finishWork
- 5 layout阶段, 调用commitLayoutEffects 执行dom操作之后
- 6 layout之后阶段,如果有useEffect的effects,就赋值给全局变量rootWithPendingPassiveEffects,useEffect的调度函数通过上面 去获取effectLists,执行对应的useEffects函数。调用ensureRootIsScheduled最后判断还有没有更新没执行
before-mutation阶段
commit阶段的第一个子阶段,此时dom还没更新,主要调用类组件的getSnapshotBeforeUpdate生命周期函数。
// finishWork表示当前执行的fiber
function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) {
const current = finishedWork.alternate; //获取rooFiber的alternate也就是current rootFiber
const flags = finishedWork.flags; //获取rootFiber的effectTag
if ((flags & Snapshot) !== NoFlags) {
// falgs有事情要做
switch (finishedWork.tag) {
case ClassComponent: {
if (current !== null) {
const prevProps = current.memoizedProps; //旧的props
const prevState = current.memoizedState; //旧的state
const instance = finishedWork.stateNode; //获取类组件的实例
// 调用类组件的getSnapshotBeforeUpdate函数
const snapshot = instance.getSnapshotBeforeUpdate(
finishedWork.elementType === finishedWork.type
? prevProps
: resolveDefaultProps(finishedWork.type, prevProps),
prevState,
);
// 将返回的快照值保存在实例的__reactInternalSnapshotBeforeUpdate上,到时候赋值给componentDidUpdate
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
}
break;
}
}
}
}
主要就调用了getSnapshotBeforeUpdate函数,并且将返回值放在了instance上的__reactInternalSnapshotBeforeUpdate
mutation阶段
mutation阶段主要是执行dom操作
- 获取effectTag,如果有ref相关的effectTag,处理ref
- 判断effectTag类型,如果是新增获取非组件父节点和兄弟节点,判断调用insert或者是append方法。对于组件,则直接循环将儿子都插入到dom上。
- 对于update,函数组建的话,直接执行useLayoutEffect和useEffect的销毁函数。类组件的话,调用componentWillUnMount
layout阶段
layout阶段主要是dom操作完成之后的事情。
主要就是调用函数组件的hooks和类组件的生命周期函数
- 类组件,调用componentDidMoutn或者componentDidUpdate,如果render有第三个参数回调函数,也会在这里执行,表示react已经挂载完毕了。
- 函数组件,调用useLayoutEffect的create函数,这里注意,useLayoutEffect是同步执行的。