前言
为了减少复杂度,本文初次流程分析都是根据下面调试代码进行,即可能存在非下面调试代码触发的初次渲染的分支代码(包括并发
或者一些其它没使用的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
对应的lane
createUpdate()
创建更新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,即NoContext
workInProgressRootRenderLanes
未设置值,为0,即NoLanes
getCurrentUpdatePriority()
未设置值,为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
中,并且处理下lanes
getRootForUpdatedFiber()
:往上寻找到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.postMessage
isPerformingWork
:跟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
的倒计时,返回false
taskQueue
所有任务还没执行完毕,直接返回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
了,因此会被直接阻止后续流程,从而阻止重复渲染问题