前言
为了减少复杂度,本文初次流程分析都是根据下面调试代码进行,即可能存在非下面调试代码触发的初次渲染的分支代码(包括并发或者一些其它没使用的API的代码)被删除,没有进行展示和分析
本文集中于初次渲染的流程分析,由于初次渲染没有进行并发渲染更新(初次渲染都是同步执行),因此本文并发更新的内容不会过多分析,而是放在后续的渲染更新文章中进行具体的分析
调试代码
const App = function () {
const [testValue, setTestValue] = React.useState("11");
console.log("重新更新");
const array = new Array(100).fill(0);
const mockLongTask = () => {
let count = 0;
for (let i = 0; i < 100; i++) {
count = (count + i) % 100;
}
console.log(count);
};
React.useEffect(() => {
console.error("useEffect执行");
}, [testValue]);
return (
<div>
<span>测试</span>
{array.map((item, index) => {
return (
<input
key={index}
value={testValue + index}
onChange={(event) => {
setTestValue(event.target.value);
mockLongTask();
}}
/>
);
})}
</div>
);
};
const domNode = document.getElementById("root");
const root = ReactDOM.createRoot(domNode);
root.render(<App />);update逻辑总结
整个渲染流程本质就是创建一个update进行处理,这里先进行总结分析,为下面的复杂分析流程提供一个思路,避免陷入细节中无法理解整体流程
update和fiber的关系
update是触发组件状态变化的原子操作,比如FunctionComponent的setState(),每一个update都包含新的状态值和优先级
type Update<State> = {
lane: Lane, // 优先级
action: Action<State>, // 更新内容(如函数或值)
next: Update<State>, // 形成链表
};fiber是描述组件的原子数据结构,每一个组件对应组件即将应用的新的props一个Fiber节点,保存了组件的类型、状态、children、副作用等等,其中也存储update数据
pendingProps代表;memoizedProps代表组件在上一次渲染中已经应用的props
type Fiber = {
tag: WorkTag, // 组件类型(函数/类组件等)
updateQueue: UpdateQueue, // 更新队列
memoizedState: any, // 组件在上一次渲染中已经应用的props
pendingProps: any, // 组件即将应用的新的props
flags: Flags, // 副作用标记,比如Placement、Update
// ...其他字段(child, sibling, return等)
};总结:
update存储在fiber.updateQueue中,fiber.updateQueue是一个单链表循环结构,存储该fiber中的所有updateupdate作用于具体的fiber,通过改变fiber的状态或者flags,驱动组件更新
update如何影响更新流程
Schedule阶段
当触发setState()或者其他更新时,会创建一个update对象,并且添加到对应fiber节点的updateQueue中
多个
update可能会被自动合并
React通过lane模型为update分配优先级,决定其处理的紧急程度,并且创建对应的task任务
Scheduler会根据优先级将task任务加入到任务队列中
如果一个
task任务还没执行完成,又有新的task任务来临,高优先级的task可以中断低优先级的task
Reconcile阶段
- 处理
Update队列:从根节点开始遍历Fiber树,对每个Fiber节点的updateQueue进行处理,并且将所有更新的lane冒泡到顶部元素 - 根据
Update的lanes跳过部分fiber的构建:在beginWork()中比对当前fiber的lanes是否跟处理Update队列形成lanes,来判断当前fiber是否需要更新,如果需要更新,则进行diff生成不同的fiber数据(并且标记上flags,比如Placement、ChildDeletion等等)
Commit阶段
根据fiber.flags的类型,执行DOM操作(如新增、删除、移动节点等等)
触发对应的生命周期函数和Effect回调(如componentDidUpdate、useLayoutEffect等等)
update的作用
- 批量处理状态变更:可以合并多次
setState,避免频繁渲染,提高性能 - 优先级调度:在并发模式下,可以区分紧急和非紧急任务进行中断,提高用户交互体验
- 增量更新优化:仅针对受影响的组件生成fiber数据,跳过未变更的fiber
update的处理流程
- 触发更新(如 setState)
- 创建队列 & 压入队列:创建
update数据并且将update加入到对应fiber的更新队列中 - Scheduler根据优先级安排任务的执行
- Render阶段:遍历Fiber树,处理对应的fiber更新队列
- 有
update数据(通过lanes判断):计算新的状态,生成新的fiber,并且打上fiber.flags - 没有
update数据(通过lanes判断):跳过
- Commit阶段:仅操作有
fiber.flags的DOM节点,根据fiber.flags执行不同的DOM操作
整体流程图
1. ReactDOM.createRoot()
一系列对象的初始化和事件初始化
function createRoot(container, options) {
//...
var root = createContainer(
container,
ConcurrentRoot,
null,
isStrictMode,
concurrentUpdatesByDefaultOverride,
identifierPrefix,
onRecoverableError
);
markContainerAsRoot(root.current, container);
//...
listenToAllSupportedEvents(rootContainerElement);
return new ReactDOMRoot(root);
}1.1 createContainer()
本质就是调用createFiberRoot():
- 创建
root=new FiberRootNode()对象(一个非常普通的对象具有一些特定的属性) - 创建
uninitializedFiber=new FiberNode()对象(一个非常普通的对象具有一些特定的属性) FiberRootNode和FiberNode相互绑定- 初始化
root对应fiber的state对象,放入fiber.memoizedState中 initializeUpdateQueue():fiber.updateQueue初始化(updateQueue也是一个非常普通的对象具有一些特定的属性)
function createContainer() {
return createFiberRoot(...);
}
function createFiberRoot(...) {
var root = new FiberRootNode(...);
var uninitializedFiber = createHostRootFiber(tag, isStrictMode);
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;
//...
var initialState = {...};
uninitializedFiber.memoizedState = initialState;
initializeUpdateQueue(uninitializedFiber);
return root;
}
function FiberRootNode(...) {
this.tag = tag;
this.containerInfo = containerInfo;
//...
}
function createHostRootFiber() {
return createFiber(HostRoot, null, null, mode);
}
var createFiber = function (...) {
return new FiberNode(...);
}
function FiberNode(...) {
this.tag = tag;
this.key = key;
//...
}
function initializeUpdateQueue(fiber) {
var queue = {
baseState: fiber.memoizedState,
firstBaseUpdate: null,
lastBaseUpdate: null,
shared: {
pending: null,
lanes: NoLanes,
},
effects: null,
};
fiber.updateQueue = queue;
}1.2 markContainerAsRoot()
root.current为创建的fiber,container为传入的dom
下面这一行本质就是container[xxxx] = root.current(数据绑定)
// markContainerAsRoot(root.current, container);
function markContainerAsRoot(hostRoot, node) {
node[internalContainerInstanceKey] = hostRoot;
}1.3 listenToAllSupportedEvents()
为rootDOM注册所有支持的原生事件监听
由于这一块比较复杂,后面会出一篇文章具体分析
listenToAllSupportedEvents,后面完成后再补充链接到这里
1.4 new ReactDOMRoot(root)
将新建的FiberRootNode对象放入到this._internalRoot中
function ReactDOMRoot(internalRoot) {
this._internalRoot = internalRoot;
}2. ReactDOMRoot.render()
本质调用的是updateContainer()方法
ReactDOMHydrationRoot.prototype.render = ReactDOMRoot.prototype.render = {
//...
updateContainer(children, root, null, null);
};而updateContainer()方法主要分为:
requestUpdateLane()创建update对应的lanecreateUpdate()创建更新update对象createUpdate()创建更新updateQueue队列enqueueUpdate()将update放入队列中scheduleUpdateOnFiber()处理队列entangleTransitionUpdate()处理Transition lanes的更新
function updateContainer(...) {
var current = container.current;
var lane = requestUpdateLane(current);
//...
var update = createUpdate(eventTime, lane);
update.payload = {
element: element,
};
//...
var root = enqueueUpdate$1(current, update, lane);
if (root !== null) {
scheduleUpdateOnFiber(root, current, lane, eventTime);
entangleTransitions(root, current, lane);
}
return lane;
}2.1 requestUpdateLane()
从ReactDOM.createRoot()的分析可以知道,一开始会传入ConcurrentRoot进行fiberRoot的创建,此时mode = ConcurrentMode
function createRoot(container, options) {
var root = createContainer(
container,
ConcurrentRoot,
null,
isStrictMode,
concurrentUpdatesByDefaultOverride,
identifierPrefix,
onRecoverableError
);
}
function createHostRootFiber(tag) {
if (tag === ConcurrentRoot) {
mode = ConcurrentMode;
} else {
mode = NoMode;
}
}由于mode = ConcurrentMode,而且
executionContext未设置值,为0,即NoContextworkInProgressRootRenderLanes未设置值,为0,即NoLanesgetCurrentUpdatePriority()未设置值,为0
因此整体代码会走到getEventPriority(),直接返回DefaultEventPriority=DefaultLane=16
function requestUpdateLane(fiber) {
var mode = fiber.mode;
if ((mode & ConcurrentMode) === NoMode) {
return SyncLane;
} else if (
(executionContext & RenderContext) !== NoContext &&
workInProgressRootRenderLanes !== NoLanes
) {
return pickArbitraryLane(workInProgressRootRenderLanes);
}
var isTransition = requestCurrentTransition() !== NoTransition;
if (isTransition) {
//...
}
var updateLane = getCurrentUpdatePriority();
if (updateLane !== NoLane) {
return updateLane;
}
var eventLane = getCurrentEventPriority();
return eventLane;
}
function getCurrentEventPriority() {
var currentEvent = window.event;
if (currentEvent === undefined) {
return DefaultEventPriority;
}
return getEventPriority(currentEvent.type);
}
function getEventPriority(domEventName) {
// domEventName = "DOMContentLoaded"
switch (domEventName) {
case "cancel":
case "click":
case "close":
case "contextmenu":
case "copy":
case "cut":
//...
default:
return DefaultEventPriority;
}
}2.2 createUpdate()创建更新队列
从requestUpdateLane()中得到lane=DefaultLane=16后创建一个update的普通对象
var update = createUpdate(eventTime, lane);function createUpdate(eventTime, lane) {
var update = {
eventTime: eventTime,
lane: lane,
tag: UpdateState,
payload: null,
callback: null,
next: null
};
return update;
}2.3 enqueueUpdate()将update放入队列中
整体逻辑比较简单,最终触发
enqueueUpdate():直接将创建的update放入到全局对象concurrentQueues中,并且处理下lanesgetRootForUpdatedFiber():往上寻找到parent,返回HostRoot的node.stateNode
function enqueueUpdate$1(fiber, update, lane) {
var updateQueue = fiber.updateQueue;
var sharedQueue = updateQueue.shared;
//...
return enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane);
}
function enqueueConcurrentClassUpdate(fiber, queue, update, lane) {
//...
enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);
return getRootForUpdatedFiber(fiber);
}function enqueueUpdate(fiber, queue, update, lane) {
concurrentQueues[concurrentQueuesIndex++] = fiber;
concurrentQueues[concurrentQueuesIndex++] = queue;
concurrentQueues[concurrentQueuesIndex++] = update;
concurrentQueues[concurrentQueuesIndex++] = lane;
concurrentlyUpdatedLanes = mergeLanes(concurrentlyUpdatedLanes, lane);
fiber.lanes = mergeLanes(fiber.lanes, lane);
var alternate = fiber.alternate;
if (alternate !== null) {
alternate.lanes = mergeLanes(alternate.lanes, lane);
}
}
function mergeLanes(a, b) {
return a | b;
}function getRootForUpdatedFiber(sourceFiber) {
var node = sourceFiber;
var parent = node.return;
while (parent !== null) {
node = parent;
parent = node.return;
}
return node.tag === HostRoot ? node.stateNode : null;
}2.4 scheduleUpdateOnFiber()处理队列
function scheduleUpdateOnFiber(root, fiber, lane, eventTime) {
//...
markRootUpdated(root, lane, eventTime);
ensureRootIsScheduled(root, eventTime);
if (
lane === SyncLane &&
executionContext === NoContext &&
(fiber.mode & ConcurrentMode) === NoMode
) {
resetRenderTimer();
flushSyncCallbacksOnlyInLegacyMode();
}
}2.4.1 markRootUpdated()
root.pendingLanes:包含了当前rootFiber树中所有待处理的update的lane(包含所有childFiber的update),可以根据pendingLanes一定范围的取值去拿到当前优先级最高的lanes,然后赋值给renderLanes,后续遍历updateQueue时可以判断当前update是否就是renderLanes的值得到当前优先级最高的update更新对象
注:因为diff是从root开始的,因此将所有更新信息都同步到root可以知道要跳过哪些更新
root.pendingLanes |= updateLane;2.4.2 ensureRootIsScheduled()
从下面代码可以知道,主要分为:
- 2.4.2.1
markStarvedLanesAsExpired()饥饿问题处理,避免低优先级一直无法执行 - 2.4.2.2
getNextLanes()获取下一个要执行的lanes(注意不是lane) - 2.4.2.3
getHighestPriorityLane()获取最高优先级的lane,首次渲染拿到getHighestPriorityLane=16 - 2.4.2.4
scheduleCallback()进行
function ensureRootIsScheduled(root, currentTime) {
var existingCallbackNode = root.callbackNode;
markStarvedLanesAsExpired(root, currentTime);
var nextLanes = getNextLanes(
root,
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes
);
if (nextLanes === NoLanes) {
//...没有任务直接返回
return;
}
// getHighestPriorityLane = 16
var newCallbackPriority = getHighestPriorityLane(nextLanes);
if (existingCallbackNode != null) {
cancelCallback$1(existingCallbackNode);
}
var newCallbackNode;
var schedulerPriorityLevel;
switch (lanesToEventPriority(nextLanes)) {
case DefaultEventPriority:
schedulerPriorityLevel = NormalPriority;
break;
//...
}
newCallbackNode = scheduleCallback(
schedulerPriorityLevel,
performConcurrentWorkOnRoot.bind(null, root)
);
root.callbackPriority = newCallbackPriority;
root.callbackNode = newCallbackNode;
}2.4.2.1 markStarvedLanesAsExpired饥饿问题
低优先级任务一直被高优先级任务打断,检查低优先级任务是否一直堵塞,强制转化为过期任务提高优先级
检查root.pendingLanes是否存在lane一直没执行,将它从pendingLanes移动到root.expiredLanes
从下面代码可以知道,
使用pickArbitraryLaneIndex()获取当前lanes最左边1对应的index,比如
lanes=000000011000=>pickArbitraryLaneIndex()=> index=4
lane=000000010000
检测这个位置的expirationTime是否存在,如果不存在
computeExpirationTime():根据lane类型,然后加上当前时间+一定的过期时间,比如现在是当前时间是12:40:00,那么它的过期时间就是12:40:00+250,250毫秒后还没执行,证明它进入饥饿状态(除了饥饿,还有死锁、活锁等并发问题)- 如果当前
lane(可以简单认为某一个任务)已经等待很久还没执行(一直被高优先级任务打断),强行将当前lane放入root.expiredLanes - 然后从
root.pendingLanes中剔除当前lane:lanes &= ~lane
root.pendingLanes: 等待执行的任务
root.expiredLanes: 已经过期的任务,必须马上执行
function markStarvedLanesAsExpired(root, currentTime) {
var pendingLanes = root.pendingLanes;
var expirationTimes = root.expirationTimes;
var lanes = pendingLanes;
while (lanes > 0) {
var index = pickArbitraryLaneIndex(lanes);
var lane = 1 << index;
var expirationTime = expirationTimes[index];
if (expirationTime === NoTimestamp) {
// 找到一个等待的lane,并且不是suspended/pinged,计算一个过期时间
if (
(lane & suspendedLanes) === NoLanes ||
(lane & pingedLanes) !== NoLanes
) {
expirationTimes[index] = computeExpirationTime(lane, currentTime);
}
} else if (expirationTime <= currentTime) {
root.expiredLanes |= lane;
}
lanes &= ~lane;
}
}
function computeExpirationTime(lane, currentTime) {
switch (lane) {
case SyncLane:
//...
return currentTime + 250;
//...
}
}2.4.2.2 getNextLanes()
由于这一块比较复杂,涉及到大量
lanes的计算,后面会出一篇文章具体分析lane,后面完成后再补充链接到这里
2.4.2.3 getHighestPriorityLane()
lanes的位置是有优先级区分的,高优先级会放在最右边
如下面代码所示,SyncLane占据了最右边的index,当我们想要获取最高优先级lane时,我们就使用lanes & -lanes获取最右边1的位置,通过与SyncLane/InputContinuousHydrationLane/DefaultLane的&运算就可以得出目前执行什么类型的lane
function getHighestPriorityLane(lanes) {
return lanes & -lanes;
}export const SyncLane: Lane = /* */ 0b0000000000000000000000000000001;
export const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000000010;
export const InputContinuousLane: Lane = /* */ 0b0000000000000000000000000000100;
export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000000001000;
export const DefaultLane: Lane = /* */ 0b0000000000000000000000000010000;
const TransitionHydrationLane: Lane = /* */ 0b0000000000000000000000000100000;
const TransitionLanes: Lanes = /* */ 0b0000000001111111111111111000000;2.4.2.4 scheduleCallback()
使用的是scheduler调度器的unstable_scheduleCallback()方法
由于这一块内容较多,将在下一个大点
scheduleCallback()中进行分析
3. scheduler
3.1 scheduleCallback()
下面代码虽然很长,但是实际的逻辑是非常简单的,主要集中在几个方面:
taskQueue:存储普通任务的队列timerQueue:存储延迟任务的队列,可以简单理解为需要延迟一定时间才执行的任务,跟普通任务相比较,普通任务是因为当前没有时间给你执行,需要等待,但是延迟任务是需要延迟一定时间才执行
而队列中的优先级是使用newTask.sortIndex = startTime进行区分,同时还具有过期时间expirationTime(防止这个任务一直被其它高优先级抢占资源无法执行,当达到过期时间后,它就可以强行去抢占资源执行)
因此下面的代码中,先处理了startTime和expirationTime(startTime + timeout),然后构建一个新的newTask,放入taskQueue或者timerQueue
- 如果当前新建
newTask属于延迟Task,判断taskQueue是不是没有任务需要执行,如果没有则触发requestHostTimeout()进行倒计时(newTask的延迟时间倒计时),看看能不能把timerQueue的任务挪到taskQueue执行 - 如果当前新建
newTask属于普通Task,加入taskQueue,然后触发requestHostCallback(flushWork)进行任务优先级队列的调度(根据sortIndex执行)
最终返回新创建的newTask给root.callbackNode = newCallbackNode(newTask)
function unstable_scheduleCallback(priorityLevel, callback, options) {
var currentTime = getCurrentTime();
var startTime;
if (typeof options === "object" && options !== null) {
var delay = options.delay;
if (typeof delay === "number" && delay > 0) {
startTime = currentTime + delay;
} else {
startTime = currentTime;
}
} else {
startTime = currentTime;
}
var timeout;
switch (priorityLevel) {
//...
case NormalPriority:
default:
timeout = NORMAL_PRIORITY_TIMEOUT;
break;
}
var expirationTime = startTime + timeout;
var newTask = {
id: taskIdCounter++,
callback: callback,
priorityLevel: priorityLevel,
startTime: startTime,
expirationTime: expirationTime,
sortIndex: -1,
};
if (startTime > currentTime) {
newTask.sortIndex = startTime;
push(timerQueue, newTask);
if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
if (isHostTimeoutScheduled) {
cancelHostTimeout();
} else {
isHostTimeoutScheduled = true;
}
requestHostTimeout(handleTimeout, startTime - currentTime);
}
} else {
newTask.sortIndex = expirationTime;
push(taskQueue, newTask);
if (!isHostCallbackScheduled && !isPerformingWork) {
isHostCallbackScheduled = true;
requestHostCallback(flushWork);
}
}
return newTask;
}那么
requestHostCallback()具体的执行逻辑是怎样的呢?是如何触发微前端的呢?返回newTask给root.callbackNode = newCallbackNode(也就是newTask)有什么作用呢?
3.2 requestHostCallback()
从下面代码可以知道,我们一开始就会根据环境初始化schedulePerformWorkUntilDeadline的赋值,我们假设目前环境支持MessageChannel,如下面代码所示
var schedulePerformWorkUntilDeadline;
if (typeof localSetImmediate === "function") {
//...
} else if (typeof MessageChannel !== "undefined") {
var channel = new MessageChannel();
var port = channel.port2;
channel.port1.onmessage = performWorkUntilDeadline;
schedulePerformWorkUntilDeadline = function () {
port.postMessage(null);
};
} else {
//...
}
function requestHostCallback(callback) {
scheduledHostCallback = callback;
if (!isMessageLoopRunning) {
isMessageLoopRunning = true;
schedulePerformWorkUntilDeadline();
}
}当我们触发requestHostCallback()时,也就是触发post.postMessage(),然后会触发channel.port1.onmessage=performWorkUntilDeadline
从下面代码可以知道,最终触发的是scheduledHostCallback(),也就是requestHostCallback(callback)传入的callback,从2.4.2.4可以知道,requestHostCallback(flushWork),因此下面的scheduledHostCallback()=flushWork()
var performWorkUntilDeadline = function () {
if (scheduledHostCallback !== null) {
var currentTime = getCurrentTime();
startTime = currentTime;
var hasTimeRemaining = true; // If a scheduler task throws, exit the current browser task so the
var hasMoreWork = true;
try {
// hasMoreWork = flushWork(hasTimeRemaining, currentTime)
hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime);
} finally {
if (hasMoreWork) {
schedulePerformWorkUntilDeadline();
} else {
isMessageLoopRunning = false;
scheduledHostCallback = null;
}
}
} else {
isMessageLoopRunning = false;
}
};3.3 flushWork()
从下面代码可以知道,有多个全局变量
isHostCallbackScheduled:用于控制requestHostCallback的调用频率,flushWork()执行后才能触发下一轮requestHostCallback(),也就是只有port.postMessage的消息送到了并且执行了回调函数,才能进行下一轮的port.postMessageisPerformingWork:跟isHostCallbackScheduled一起控制requestHostCallback的调用频率,如下面unstable_scheduleCallback()代码所示,只有!isHostCallbackScheduled && !isPerformingWork才会触发requestHostCallback()进行微任务的调用
因此只有
port.postMessage的消息送到了并且执行了回调函数,并且执行了workLoop()结束后,才能触发下一轮requestHostCallback()当然在
workLoop()还没执行完毕的过程中,可能会不断往taskQueue加入新的task,因此这里只是控制目前只有一个微任务在执行而已
isHostTimeoutScheduled:标记requestHostTimeout()是否已经执行,主要是进行delay任务类型的检测setTimeout(),如果当前已经启动setTimeout()检测,那么isHostTimeoutScheduled=true,此时已经触发flushWork(执行的是taskQueue的内容,没有精力给timeQueue的任务执行,因此先暂停timeQueue的任务检测)
taskQueue存放的是正常的update更新的task,timeQueue存放的是带有延迟的update更新的task
function flushWork(hasTimeRemaining, initialTime) {
isHostCallbackScheduled = false;
if (isHostTimeoutScheduled) {
isHostTimeoutScheduled = false;
cancelHostTimeout();
}
isPerformingWork = true;
var previousPriorityLevel = currentPriorityLevel;
try {
return workLoop(hasTimeRemaining, initialTime);
} finally {
currentTask = null;
currentPriorityLevel = previousPriorityLevel;
isPerformingWork = false;
}
}
function unstable_scheduleCallback(...) {
//...
var newTask = {
id: taskIdCounter++,
callback: callback,
priorityLevel: priorityLevel,
startTime: startTime,
expirationTime: expirationTime,
sortIndex: -1,
};
//...
push(taskQueue, newTask);
//...
if (!isHostCallbackScheduled && !isPerformingWork) {
isHostCallbackScheduled = true;
requestHostCallback(flushWork);
}
return newTask;
}在
flushWork()中分析中,我们知道,主要核心代码就是触发workLoop()
3.4 workLoop()
在分析workLoop()之前,我们先分析一个出现在workLoop()中出现很多次的方法advanceTimers
我们检测timerQueue栈顶的任务是否有任务已经到达延迟时间,如果已经到达延迟时间,则将它从timerQueue移入到taskQueue中,否则就什么都不执行
如果有一些任务已经被取消,该任务是废弃永远不会触发的任务,则从
timerQueue中剔除
function advanceTimers(currentTime) {
// Check for tasks that are no longer delayed and add them to the queue.
var timer = peek(timerQueue);
while (timer !== null) {
if (timer.callback === null) {
// Timer was cancelled.
pop(timerQueue);
} else if (timer.startTime <= currentTime) {
// Timer fired. Transfer to the task queue.
pop(timerQueue);
timer.sortIndex = timer.expirationTime;
push(taskQueue, timer);
} else {
// Remaining timers are pending.
return;
}
timer = peek(timerQueue);
}
}从下面workLoop()可以知道,整体代码非常多,但是逻辑其实非常简单
- 从
taskQueue中取出task,执行task.callback,如果返回continuationCallback,则更新task.callback=continuationCallback - 继续从
taskQueue中取出task,直到taskQueue所有任务都执行完毕/目前没有时间执行:!hasTimeRemaining || shouldYieldToHost() - 然后判断当前是因为
taskQueue所有任务都执行完毕还是因为时间不够taskQueue所有任务都执行完毕,开始requestHostTimeout()检测timerQueue的倒计时,返回falsetaskQueue所有任务还没执行完毕,直接返回true
在整个
workLoop()的执行过程,会不断调用advanceTimers()检测是否能将timerQueue的任务放入到taskQueue
function workLoop(hasTimeRemaining, initialTime) {
var currentTime = initialTime;
advanceTimers(currentTime);
currentTask = peek(taskQueue);
while (currentTask !== null) {
if (
currentTask.expirationTime > currentTime &&
(!hasTimeRemaining || shouldYieldToHost())
) {
// 任务还没过期,并且已经没有时间给workLoop,则直接中断workLoop()执行
break;
}
var callback = currentTask.callback;
currentTask.callback = null;
var didUserCallbackTimeout = currentTask.expirationTime <= currentTime;
var continuationCallback = callback(didUserCallbackTimeout);
currentTime = getCurrentTime();
if (typeof continuationCallback === "function") {
// 如果continuationCallback还存在
currentTask.callback = continuationCallback;
} else {
// 如果continuationCallback不允许,从taskQueue中剔除task
if (currentTask === peek(taskQueue)) {
pop(taskQueue);
}
}
advanceTimers(currentTime);
currentTask = peek(taskQueue);
}
if (currentTask !== null) {
// 如果taskQueue还没执行完毕
return true;
} else {
// 如果taskQueue的任务已经执行完毕,尝试执行timerQueue
var firstTimer = peek(timerQueue);
if (firstTimer !== null) {
requestHostTimeout(handleTimeout, firstTimer.startTime - currentTime);
}
return false;
}
}4. performConcurrentWorkOnRoot()-task.callback具体执行方法
从下面代码,我们主要分为几块内容:
flushPassiveEffects()执行effect?????后面再继续分析shouldTimeSlice的判断:是否可以进行时间切片,如果可以则触发renderRootConcurrent(),否则触发renderRootSync()- 检测
render是否已经执行完毕,如果没有则再次出发renderRootSync() - 如果
render已经执行完毕,则触发commit阶段,触发finishConcurrentRender,通过一系列复杂逻辑处理后,通过root.callbackNode === originalCallbackNode来判断是否已经完全执行完毕,如果没有,则返回continuationCallback,然后currentTask.callback = continuationCallback - 当然执行完毕了,会触发
ensureRootIsScheduled():从lanes取出下一个lane的task创建以及requestHostCallback()触发微任务
如果此时
taskQueue还有很多任务,那么workLoop()就还是会继续执行,那么isPerformingWork = false,requestHostCallback()就不会再次触发,因此ensureRootIsScheduled()只会触发从lanes取出下一个优先级最高lane进行task创建,然后放入到taskQueue当然这个
task有可能优先级较高,放在taskQueue栈顶,然后被workLoop()取出执行:currentTask = peek(taskQueue)
function performConcurrentWorkOnRoot(root, didTimeout) {
var originalCallbackNode = root.callbackNode;
var didFlushPassiveEffects = flushPassiveEffects();
if (didFlushPassiveEffects) {
// effect的cleanup()函数可能会取消当前task
// root.callbackNode改变则说明已经取消
if (root.callbackNode !== originalCallbackNode) {
return null;
}
}
//...
var shouldTimeSlice =
!includesBlockingLane(root, lanes) &&
!includesExpiredLane(root, lanes) &&
!didTimeout;
var exitStatus = shouldTimeSlice
? renderRootConcurrent(root, lanes)
: renderRootSync(root, lanes);
if (exitStatus !== RootInProgress) {
//...检测是否可能还没render完毕
var renderWasConcurrent = !includesBlockingLane(root, lanes);
var finishedWork = root.current.alternate;
if (
renderWasConcurrent &&
!isRenderConsistentWithExternalStores(finishedWork)
) {
exitStatus = renderRootSync(root, lanes);
}
// commit阶段
var finishedWork = root.current.alternate;
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
finishConcurrentRender(root, exitStatus, lanes);
}
// 从lanes取出下一个lane的task创建以及requestHostCallback触发微任务
ensureRootIsScheduled(root, now());
if (root.callbackNode === originalCallbackNode) {
// 当前root.callbackNode仍然没有改变,因此这个任务还没完全执行完毕
// 正常finishConcurrentRender()阶段会将root.callbackNode=null
return performConcurrentWorkOnRoot.bind(null, root);
}
return null;
}shouldTimeSlice的计算依赖于
includesBlockingLane()和includesExpiredLane(),那么这两个函数执行了什么逻辑呢?
4.1 shouldTimeSlice
SyncDefaultLanes代表优先级非常高的lane,不能进行切片慢慢执行,BlockingLane=InputContinuousHydrationLane | InputContinuousLane | DefaultHydrationLane | DefaultLane
root.expiredLanes代表处于饥饿状态的lane(比如一直被高优先级抢占的低优先级lane),需要马上执行,不能进行时间切片
function includesBlockingLane(root, lanes) {
var SyncDefaultLanes =
InputContinuousHydrationLane |
InputContinuousLane |
DefaultHydrationLane |
DefaultLane;
return (lanes & SyncDefaultLanes) !== NoLanes;
}
function includesExpiredLane(root, lanes) {
// This is a separate check from includesBlockingLane because a lane can
// expire after a render has already started.
return (lanes & root.expiredLanes) !== NoLanes;
}4.2 renderRootSync()
由于这小节内容较多,我们移入到5. renderRootSync()进行具体的分析
4.3 finishConcurrentRender()
由于这小节内容较多,我们移入到6. finishConcurrentRender()进行具体的分析
5. renderRootSync()
初次渲染时,workInProgressRoot初始值为null, workInProgressRootRenderLanes的初始值为NoLanes = 0,因此会触发prepareFreshStack()进行一些准备工作,然后触发核心方法
workLoopSync()也就是performUnitOfWork()
function renderRootSync(root, lanes) {
var prevExecutionContext = executionContext;
executionContext |= RenderContext;
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
//...
workInProgressTransitions = getTransitionsForLanes(); // enableTransitionTracing=false,返回null
prepareFreshStack(root, lanes);
}
do {
workLoopSync();
break;
} while (true);
executionContext = prevExecutionContext;
workInProgressRoot = null;
workInProgressRootRenderLanes = NoLanes;
return workInProgressRootExitStatus;
}
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}5.1 prepareFreshStack
从下面可以知道,主要触发
rootWorkInProgress = createWorkInProgress(root.current)根据当前rootFiber创建一个新的fiber,赋值给全局变量workInProgressRoot- 进行其它多个全局变量,如
workInProgressRootRenderLanes、workInProgress等等 - 触发
finishQueueingConcurrentUpdates()处理concurrentQueues全局变量,构建concurrentQueues中所有update成为一个链表结构
最终返回目前创建的fiber:rootWorkInProgress(就是全局变量workInProgressRoot)
function prepareFreshStack(root, lanes) {
//...
workInProgressRoot = root;
var rootWorkInProgress = createWorkInProgress(root.current, null);
workInProgress = rootWorkInProgress;
workInProgressRootRenderLanes = subtreeRenderLanes = workInProgressRootIncludedLanes = lanes;
//...
finishQueueingConcurrentUpdates();
return rootWorkInProgress;
}5.1.1 createWorkInProgress()
如下面代码所示,就是利用当前rootFiber创建一个新的fiber
注意
workInProgress.alternate = current和current.alternate = workInProgress,并不是直接复用旧的fiber Root,而是根据current新建一个fiber Root
function createWorkInProgress(current, pendingProps) {
var workInProgress = current.alternate;
if (workInProgress === null) {
workInProgress = createFiber(
current.tag,
pendingProps,
current.key,
current.mode
);
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
//...
}
//...workInProgress各种根据current赋值
workInProgress.childLanes = current.childLanes;
workInProgress.lanes = current.lanes;
//...
return workInProgress;
}5.1.2 finishQueueingConcurrentUpdates()
将concurrentQueues的内容拿出来(包括queue、fiber、lane、update),形成一个链表结构,然后调用markUpdateLaneFromFiberToRoot()更新lanes以及将children的lane都更新到fiber.childrenLanes中(为后面diff做准备,可以知道哪些child可以跳过更新)
function finishQueueingConcurrentUpdates() {
var endIndex = concurrentQueuesIndex;
concurrentQueuesIndex = 0;
concurrentlyUpdatedLanes = NoLanes;
var i = 0;
while (i < endIndex) {
var fiber = concurrentQueues[i];
concurrentQueues[i++] = null;
var queue = concurrentQueues[i];
concurrentQueues[i++] = null;
var update = concurrentQueues[i];
concurrentQueues[i++] = null;
var lane = concurrentQueues[i];
concurrentQueues[i++] = null;
if (queue !== null && update !== null) {
var pending = queue.pending;
if (pending === null) {
// This is the first update. Create a circular list.
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
}
if (lane !== NoLane) {
markUpdateLaneFromFiberToRoot(fiber, update, lane);
}
}
}function markUpdateLaneFromFiberToRoot(sourceFiber, update, lane) {
// Update the source fiber's lanes
sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);
var alternate = sourceFiber.alternate;
if (alternate !== null) {
alternate.lanes = mergeLanes(alternate.lanes, lane);
}
//...
while (parent !== null) {
parent.childLanes = mergeLanes(parent.childLanes, lane);
alternate = parent.alternate;
if (alternate !== null) {
alternate.childLanes = mergeLanes(alternate.childLanes, lane);
}
//...
node = parent;
parent = parent.return;
}
//...
}经过
prepreFreshStatck()???这个方法到底实现了什么?然后触发workLoopSync()
5.2 workLoopSync()->performUnitOfWork()
从下面代码可以知道,renderRootSync()核心就是触发workLoopSync()->performUnitOfWork(),因此我们主要分析performUnitOfWork()即可
function workLoopSync() {
// Already timed out, so perform work without checking if we need to yield.
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(unitOfWork) {
var current = unitOfWork.alternate;
var next;
//...
next = beginWork(current, unitOfWork, subtreeRenderLanes);
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// If this doesn't spawn new work, complete the current work.
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
//...
}上面代码融合起来就是:
function workLoopSync() {
while (workInProgress !== null) {
var current = unitOfWork.alternate;
var next;
//...
next = beginWork(current, unitOfWork, subtreeRenderLanes);
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// If this doesn't spawn new work, complete the current work.
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
//...
}
}上面代码逻辑极其简单,就是触发beginWork()和completeUnitOfWork()
从源码上的名称,我们可以知道,
beginWork()拿到的就下一个unitOfWork(根据react源码未打包版本,就是一个Fiber),那么beginWork()和completeUnitOfWork()的执行顺序是怎样的呢?
如下图所示,从调试代码打印的信息可以知道,beginWork()和completeUnitOfWork()的执行顺序是一个深度优先遍历(神似二叉树的后序遍历)
beginWork()和completeUnitOfWork()的执行顺序在代码中是如何实现的呢?
function workLoopSync() {
while (workInProgress !== null) {
var current = unitOfWork.alternate;
var next;
//...
next = beginWork(current, unitOfWork, subtreeRenderLanes);
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// If this doesn't spawn new work, complete the current work.
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
//...
}
}
function completeUnitOfWork(unitOfWork) {
var completedWork = unitOfWork;
do {
var current = completedWork.alternate;
var returnFiber = completedWork.return;
var next = completeWork(current, completedWork, subtreeRenderLanes);
if (next !== null) {
workInProgress = next;
return;
}
var siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
workInProgress = siblingFiber;
return;
}
completedWork = returnFiber;
workInProgress = completedWork;
} while (completedWork !== null);
if (workInProgressRootExitStatus === RootInProgress) {
workInProgressRootExitStatus = RootCompleted;
}
}上面的代码看起来很多,其实总结起来就是:
5.2.1 build-your-own-react分析对比
由于beginWork()和completeWork()章节内容较多,并且不容易理解,因此我们这里需要借助下其它文章React初探-构建最小化的React文章分析一波,可以有助于我们更好理解beginWork()和completeWork()的流程,如下面流程图所示,我们可以简单对比下(可能部分细节不太准确,但是大体方向是对的):
React18的beginWork()其实就是下图performUnitOfWork()中的createDom()和reconcileChildren(),用户操作后会形成新的fiber树,新的fiber树会比较旧的fiber树,看看哪些DOM可以复用!因此会通过reconcileChildren()进行比较,如果可以复用,就进行复用旧的fiber树上fiber对应的dom;如果无法复用,则创建新的dom,最终形成一棵新的fiber树(上面持有DOM,离屏DOM,还没挂载在<html>上,等待最终commit才挂载)React18的completeWork()其实就是下图commitRoot(),记住这个方法触发必须是完成this.nextUnitOfWork为空,也就是目前已经performUnitOfWork()已经执行完毕了,新的fiber树已经构建完成了,并且新的fiber树每一个fiber已经构建好真实DOM,在commitRoot(),就是从根节点开始,从根节点的真实DOM根据新的fiber树对应的关系开始组装DOM,比如dom.appendChild()、dom.removeChild()等等- 根据
effectTag进行新增、替换、删除等真实DOM的操作 - 如果遇到替换,直接更新旧的
fiber树上fiber对应的DOM属性即可! - 然后递归调用
commit(fiber.child)和commit(fiber.sibling)
- 根据
5.2.2 beginWork()
初次渲染主要根据workInProgress.tag进行不同逻辑的调用,比如
IndeterminateComponent:函数组件mount时进入的分支FunctionComponent:函数组件update时进入的分支ClassComponent:类组件进入的分支HostComponent:原生元素,比如div进入的分支
function beginWork(current, workInProgress, renderLanes) {
didReceiveUpdate = false;
workInProgress.lanes = NoLanes;
switch (workInProgress.tag) {
case IndeterminateComponent:
return mountIndeterminateComponent(
current,
workInProgress,
workInProgress.type,
renderLanes
);
case HostRoot:
return updateHostRoot(current, workInProgress, renderLanes);
case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes);
case HostText:
return updateHostText(current, workInProgress);
case FunctionComponent: {
//...
}
case ClassComponent: {
//....
}
//...
}
}5.2.3 completeUnitOfWork->completeWork
completeUnitOfWork()就是一个深度遍历的非递归版本
function completeUnitOfWork(unitOfWork) {
var completedWork = unitOfWork;
do {
var current = completedWork.alternate;
var returnFiber = completedWork.return;
var next = completeWork(current, completedWork, subtreeRenderLanes);
if (next !== null) {
workInProgress = next;
return;
}
var siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
workInProgress = siblingFiber;
return;
}
completedWork = returnFiber;
workInProgress = completedWork;
} while (completedWork !== null);
if (workInProgressRootExitStatus === RootInProgress) {
workInProgressRootExitStatus = RootCompleted;
}
}从下面的流程图我们可以知道,completedWork()类似于后序遍历,会从child->child.sibling->parent不断向上遍历
而completeWork()执行内容与beginWork()类似,都是根据fiber.tag进行不同方法的调用
function completeWork(current, workInProgress, renderLanes) {
var newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case ClassComponent: {
bubbleProperties(workInProgress);
return null;
}
case HostRoot: {
//...
}
//...
}
}5.2.3.1 bubbleProperties()
通过mergeLanes(_child.lanes, _child.childLanes),不断将children的lanes冒泡到parent
在遍历过程中,会不断将目前子节点的
lanes和flags都合并到当前节点的childLanes和subtreeFlags,这样做的目的是当我们从root开始向下渲染时,我们不用深度遍历到某一个子节点,我们就能从某一个父节点知道子节点是否需要更新,以及是否存在effect需要处理!
function bubbleProperties(completedWork) {
var didBailout =
completedWork.alternate !== null &&
completedWork.alternate.child === completedWork.child;
var newChildLanes = NoLanes;
var subtreeFlags = NoFlags;
//...
if (!didBailout) {
var _child = completedWork.child;
while (_child !== null) {
newChildLanes = mergeLanes(
newChildLanes,
mergeLanes(_child.lanes, _child.childLanes)
);
subtreeFlags |= _child.subtreeFlags;
subtreeFlags |= _child.flags;
_child.return = completedWork;
_child = _child.sibling;
}
completedWork.subtreeFlags |= subtreeFlags;
} else {
//...
}
completedWork.childLanes = newChildLanes;
return didBailout;
}由于我们执行completeWork()是从child->parent,因此我们可以将所有child fiber的lanes、childLanes、flags、subtreeFlags都合并到parent对应的childLanes和subtreeFlags属性,这样我们可以直接通过判断root数据中的childLanes和subtreeFlags属性轻易得到children的一些属性,比如更新优先级、更新类型(删除、新增、替换),而不用每次都深度遍历才能知道children的一些属性
6. finishConcurrentRender()
回到performConcurrentWorkOnRoot()的分析中,我们上面已经分析了renderRootSync()的render阶段,现在进入了commit阶段
主要核心代码为root.finishedWork=root.current.alternate和finishConcurrentRender(),如下面代码所示
function performConcurrentWorkOnRoot(root, didTimeout) {
//...
var shouldTimeSlice =
!includesBlockingLane(root, lanes) &&
!includesExpiredLane(root, lanes) &&
!didTimeout;
var exitStatus = shouldTimeSlice
? renderRootConcurrent(root, lanes)
: renderRootSync(root, lanes);
// commit阶段
var finishedWork = root.current.alternate;
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
finishConcurrentRender(root, exitStatus, lanes);
//...
}commit阶段最终是提交fiber Root的复制fiber进行提交,然后触发finishConcurrentRender()方法,从而最终触发commitRootImpl()方法
var finishedWork = root.current.alternate;
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
finishConcurrentRender(root, exitStatus, lanes);
function finishConcurrentRender(root, exitStatus, lanes) {
switch (exitStatus) {
//...
case RootCompleted: {
commitRoot(root, workInProgressRootRecoverableErrors, workInProgressTransitions);
break;
}
}
}
function commitRoot(root, recoverableErrors, transitions) {
//...
commitRootImpl(root, recoverableErrors, transitions, previousUpdateLanePriority);
}而commitRootImpl()的核心代码如下所示:
- 判断
subtreeHasEffects(root元素的children存在effect)和rootHasEffect(root元素存在effect)然后调用commitBeforeMutationEffects()commitMutationEffects()commitLayoutEffects()
- 调用异步更新
ensureRootIsScheduled() - 调用同步更新
flushSyncCallbacks()
function commitRootImpl(...) {
//...
var subtreeHasEffects =
(finishedWork.subtreeFlags &
(BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
NoFlags;
var rootHasEffect =
(finishedWork.flags &
(BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
NoFlags;
if (subtreeHasEffects || rootHasEffect) {
const prevExecutionContext = executionContext;
executionContext |= CommitContext;
commitBeforeMutationEffects(root, finishedWork);
commitMutationEffects(root, finishedWork, lanes);
commitLayoutEffects(finishedWork, root, lanes);
executionContext = prevExecutionContext;
} else {
//...no effects
}
ensureRootIsScheduled(root, now());
flushSyncCallbacks();
}而
BeforeMutationMask、MutationMask、LayoutMask、PassiveMask又代表什么意思呢?
6.1 flags种类
var BeforeMutationMask = Update | Snapshot;
var MutationMask = Placement | Update | ChildDeletion | ContentReset | Ref | Hydrating | Visibility;
var LayoutMask = Update | Callback | Ref | Visibility;
var PassiveMask = Passive | ChildDeletion;6.2 commitBeforeMutationEffects()
从下面commitBeforeMutationEffects()代码我们可以知道,本质是深度优先遍历,我们从根fiber开始遍历,不停将nextEffect赋值给fiber.child,直到叶子fiber,然后触发commitBeforeMutationEffects_complete()
function commitBeforeMutationEffects(root, firstChild) {
//...
nextEffect = firstChild;
commitBeforeMutationEffects_begin();
}
function commitBeforeMutationEffects_begin() {
while (nextEffect !== null) {
var fiber = nextEffect;
var child = fiber.child;
if (
(fiber.subtreeFlags & BeforeMutationMask) !== NoFlags &&
child !== null
) {
child.return = fiber;
nextEffect = child;
} else {
commitBeforeMutationEffects_complete();
}
}
}在commitBeforeMutationEffects_complete()中,我们调用
commitBeforeMutationEffectsOnFiber(fiber)- 然后寻找当前
fiber的sibling,如果没有sibling,则将当前nextEffect移动到fiber.return,类似于二叉树的后序遍历,先处理所有children,然后再回到parent,然后继续触发commitBeforeMutationEffectsOnFiber(fiber)
function commitBeforeMutationEffects_complete() {
while (nextEffect !== null) {
var fiber = nextEffect;
commitBeforeMutationEffectsOnFiber(fiber);
var sibling = fiber.sibling;
if (sibling !== null) {
sibling.return = fiber.return;
nextEffect = sibling;
return;
}
nextEffect = fiber.return;
}
}6.2.1 commitBeforeMutationEffectsOnFiber()
处理
Snapshot相关标记
在commitBeforeMutationEffectsOnFiber()的代码中,我们可以清晰看到,就是处理fiber.flag等于Snapshot相关的逻辑,涉及到ClassComponent和HostText两种类型
ClassComponent:调用getSnapshotBeforeUpdate()生命周期
如果你实现了
getSnapshotBeforeUpdate,React 会在 React 更新 DOM 之前时直接调用它。它使你的组件能够在 DOM 发生更改之前捕获一些信息(例如滚动的位置)。此生命周期方法返回的任何值都将作为参数传递给componentDidUpdate
HostText: 文本节点调用clearContainer()???清空???
function commitBeforeMutationEffectsOnFiber(finishedWork) {
var current = finishedWork.alternate;
var flags = finishedWork.flags;
if ((flags & Snapshot) !== NoFlags) {
switch (finishedWork.tag) {
case ClassComponent: {
if (current !== null) {
var prevProps = current.memoizedProps;
var prevState = current.memoizedState;
var instance = finishedWork.stateNode; // We could update instance props and state here,
var snapshot = instance.getSnapshotBeforeUpdate(
finishedWork.elementType === finishedWork.type
? prevProps
: resolveDefaultProps(finishedWork.type, prevProps),
prevState
);
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
}
break;
}
case HostRoot: {
var root = finishedWork.stateNode;
clearContainer(root.containerInfo);
break;
}
}
}
}6.3 commitMutationEffects()
function commitMutationEffects(root, finishedWork, committedLanes) {
//...
commitMutationEffectsOnFiber(finishedWork, root);
//...
}从下面代码可以知道,根据不同fiber.tag进行处理
- 先调用
recursivelyTraverseMutationEffects() - 再触发
commitReconciliationEffects() - 最终根据不同
tag触发不同的逻辑,主要涉及到Update、Placement、Deletion、Ref、Hydrating等多种标记的操作
function commitMutationEffectsOnFiber(finishedWork, root, lanes) {
//...
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
if (flags & Update) {
commitHookEffectListUnmount(Insertion | HasEffect, ...);
commitHookEffectListMount(Insertion | HasEffect, finishedWork);
commitHookEffectListUnmount(Layout | HasEffect, ...);
}
return;
}
case ClassComponent: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
if (flags & Ref) {
//...
}
return;
}
case HostComponent: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
if (flags & Ref) {
//...
}
if (finishedWork.flags & ContentReset) {
//...
}
if (flags & Update) {
//...
}
return;
}
case HostText: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
if (flags & Update) {
//...
}
return;
}
case HostRoot: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
if (flags & Update) {
//...
}
return;
}
case OffscreenComponent: {
//...
}
//...还有多种类型
default: {
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
return;
}
}
}6.3.1 recursivelyTraverseMutationEffects()
在commitMutationEffectsOnFiber()中高频出现的recursivelyTraverseMutationEffects()是为了
- 处理当前
fiber.deletions,在reconcileChildFibers()中进行fiber.deletions数据的添加(也就是fiber.children中已经被移除的数据) - 然后触发
fiber.child进行commitMutationEffectsOnFiber()=>fiber.child处理完成,就处理fiber.child.sibling,触发commitMutationEffectsOnFiber()
总结:处理fiber.childrenDeletion集合 + 往下遍历fiber.child + fiber.child.sibling进行递归调用commitMutationEffectsOnFiber()
由于内容较多,这个小节不会对
commitDeletionEffects()进行详细分析
function recursivelyTraverseMutationEffects(root, parentFiber, lanes) {
var deletions = parentFiber.deletions;
if (deletions !== null) {
for (var i = 0; i < deletions.length; i++) {
var childToDelete = deletions[i];
commitDeletionEffects(root, parentFiber, childToDelete);
}
}
// MutationMask = Placement | Update | ChildDeletion | ContentReset | Ref | Hydrating | Visibility;
if (parentFiber.subtreeFlags & MutationMask) {
var child = parentFiber.child;
while (child !== null) {
commitMutationEffectsOnFiber(child, root);
child = child.sibling;
}
}
}
function ChildReconciler(shouldTrackSideEffects) {
function deleteChild(returnFiber, childToDelete) {
if (!shouldTrackSideEffects) {
return;
}
var deletions = returnFiber.deletions;
if (deletions === null) {
returnFiber.deletions = [childToDelete];
returnFiber.flags |= ChildDeletion;
} else {
deletions.push(childToDelete);
}
}
}6.3.2 commitReconciliationEffects()
处理Placement和Hydrating相关更新,触发commitPlacement()
function commitReconciliationEffects(finishedWork) {
var flags = finishedWork.flags;
if (flags & Placement) {
commitPlacement(finishedWork);
finishedWork.flags &= ~Placement;
}
if (flags & Hydrating) {
finishedWork.flags &= ~Hydrating;
}
}6.4 commitLayoutEffects()
与commitBeforeMuationEffects()逻辑一致,先处理children->children.sibling->parent->finishedWork
实际执行方法还是commitLayoutEffectOnFiber()
注意:
fiber.subtreeFlags代表的是它的所有深度层级children的flags集合!
function commitLayoutEffects(finishedWork, root, committedLanes) {
nextEffect = finishedWork;
commitLayoutEffects_begin(finishedWork, root, committedLanes);
}
function commitLayoutEffects_begin(subtreeRoot, root, committedLanes) {
while (nextEffect !== null) {
var fiber = nextEffect;
var firstChild = fiber.child;
if ((fiber.subtreeFlags & LayoutMask) !== NoFlags && firstChild !== null) {
firstChild.return = fiber;
nextEffect = firstChild;
} else {
commitLayoutMountEffects_complete(subtreeRoot, root, committedLanes);
}
}
}
function commitLayoutMountEffects_complete(subtreeRoot, root, committedLanes) {
while (nextEffect !== null) {
var fiber = nextEffect;
if ((fiber.flags & LayoutMask) !== NoFlags) {
var current = fiber.alternate;
commitLayoutEffectOnFiber(root, current, fiber, committedLanes);
}
if (fiber === subtreeRoot) {
// commitLayoutEffects()>nextEffect=subtreeRoot,
// 如果相等,则说明遍历到一开始的parent,已经全部都处理完毕了,因此是后序遍历,先处理children->parent
nextEffect = null;
return;
}
var sibling = fiber.sibling;
if (sibling !== null) {
sibling.return = fiber.return;
nextEffect = sibling;
return;
}
nextEffect = fiber.return;
}
}6.4.1 commitLayoutEffectOnFiber()
- 处理
Update | Callback | Ref | Visibility相关flags标记 - 根据
fiber.tag调用不同方法进行处理
我们将在下面
fiber.tag具体展开分析调用了什么方法
function commitLayoutEffectOnFiber(
finishedRoot,
current,
finishedWork,
committedLanes
) {
// var LayoutMask = Update | Callback | Ref | Visibility;
if ((finishedWork.flags & LayoutMask) !== NoFlags) {
switch (finishedWork.tag) {
case HostComponent:
//...
}
}
}问题总结
多次ensureRootIsScheduled触发为什么不会重复渲染?
问题详解
在初次渲染时,会触发一次 ensureRootIsScheduled(),在这个 ensureRootIsScheduled() 的流程中:
- 在
performConcurrentWorkOnRoot()中,commit阶段结束时会触发一次ensureRootIsScheduled() - 在
commit阶段的结尾逻辑中,其实也触发了ensureRootIsScheduled()
为什么这么多次不会导致重复渲染呢?
答案
那是因为首次渲染时,在updateContainer()中会走到getEventPriority(),直接返回DefaultEventPriority=DefaultLane=16
但是在其它逻辑中触发 ensureRootIsScheduled(),拿到的 root.pendingLanes 就不是 16,而是 0 了,因此会被直接阻止后续流程,从而阻止重复渲染问题