Open Little-Gee opened 1 year ago
一开始我们讲了整个渲染的简略流程,之后又梳理了几个关键节点,现在我们深入每个关键节点看看它们到底做了什么。
第一个节点是触发状态更新,之前举的例子是应用的启动,其实这只是触发状态更新的方式之一。 在 React 中,有以下几种触发状态更新的方法:
看上去有好几种,实际上我们可以给他们归归类:
发起组件更新这一类型其实还可以根据组件类型分成两类:ClassComponent和FunctionComponent,而应用初始化这种类型对应的组件是HostRoot,所以以组件类型来划分的话,就是下面这样:
ClassComponent
FunctionComponent
HostRoot
(这么划分是因为后面的update对象有两种类型)
update对象
虽然这几种方式看起来使用场景不同,实际上内部流程中有很多相似之处。
应用的启动中我们在最后讲到更新的时候会调用updateContainer这个函数:
updateContainer
export function updateContainer( element: ReactNodeList, container: OpaqueRoot, parentComponent: ?React$Component<any, any>, callback: ?Function, ): Lane { // 省略 // 获取当前时间戳 const current = container.current; const eventTime = requestEventTime(); // 省略 // 1.创建一个优先级变量(车道模型) const lane = requestUpdateLane(current); // 省略 const context = getContextForSubtree(parentComponent); if (container.context === null) { container.context = context; } else { container.pendingContext = context; } // 省略 // 2.根据车道优先级,创建 update 对象 const update = createUpdate(eventTime, lane); update.payload = {element}; // callback为 ReactDOM.render 的第三个参数 —— 回调函数 callback = callback === undefined ? null : callback; if (callback !== null) { // 省略 update.callback = callback; } // 3.将生成的 update 加入 updateQueue enqueueUpdate(current, update); // 4.调度更新 scheduleUpdateOnFiber(current, lane, eventTime); return lane; }
可以看到里面包含4个步骤:
调度更新
我们再来看看ClassComponent的更新:
const classComponentUpdater = { isMounted, enqueueSetState(inst, payload, callback) { const fiber = getInstance(inst); const eventTime = requestEventTime(); const lane = requestUpdateLane(fiber); const update = createUpdate(eventTime, lane); update.payload = payload; if (callback !== undefined && callback !== null) { // 省略 update.callback = callback; } enqueueUpdate(fiber, update); scheduleUpdateOnFiber(fiber, lane, eventTime); // 省略 if (enableSchedulingProfiler) { markStateUpdateScheduled(fiber, lane); } }, enqueueReplaceState(inst, payload, callback) { const fiber = getInstance(inst); const eventTime = requestEventTime(); const lane = requestUpdateLane(fiber); const update = createUpdate(eventTime, lane); update.tag = ReplaceState; update.payload = payload; if (callback !== undefined && callback !== null) { // 省略 update.callback = callback; } enqueueUpdate(fiber, update); scheduleUpdateOnFiber(fiber, lane, eventTime); // 省略 if (enableSchedulingProfiler) { markStateUpdateScheduled(fiber, lane); } }, enqueueForceUpdate(inst, callback) { const fiber = getInstance(inst); const eventTime = requestEventTime(); const lane = requestUpdateLane(fiber); const update = createUpdate(eventTime, lane); update.tag = ForceUpdate; if (callback !== undefined && callback !== null) { // 省略 update.callback = callback; } enqueueUpdate(fiber, update); scheduleUpdateOnFiber(fiber, lane, eventTime); // 省略 if (enableSchedulingProfiler) { markForceUpdateScheduled(fiber, lane); } }, };
里面有enqueueSetState、enqueueReplaceState、enqueueForceUpdate三个方法,从名字就可以看出对应setState、replaceState、forceUpdate(enqueueReplaceState是在内部用的,可以不管)。 setState和forceUpdate的代码如下:
enqueueSetState
enqueueReplaceState
enqueueForceUpdate
setState
replaceState
forceUpdate
Component.prototype.setState = function(partialState, callback) { // 省略 this.updater.enqueueSetState(this, partialState, callback, 'setState'); };
Component.prototype.forceUpdate = function(callback) { this.updater.enqueueForceUpdate(this, callback, 'forceUpdate'); };
可以看到这两个是原型链上的方法,内部分别调用了`enqueueSetState`和`enqueueForceUpdate`。 而在上面的具体方法中,我们又可以看到这几个方法都是差不多的,也是`创建优先级变量 -> 创建 update 对象 -> update 加入 updateQueue -> 调度更新`这个流程。 ## 3.FunctionComponent 最后再来看看`FunctionComponent`的更新: ```javascript function dispatchAction<S, A>( fiber: Fiber, queue: UpdateQueue<S, A>, action: A, ) { // 省略 const eventTime = requestEventTime(); const lane = requestUpdateLane(fiber); // 创建 update对象 const update: Update<S, A> = { lane, action, eagerReducer: null, eagerState: null, next: (null: any), }; // 将 update对象 添加到 hook.queue.pending 队列 const pending = queue.pending; if (pending === null) { // 首个 update,创建一个环形链表 update.next = update; } else { update.next = pending.next; pending.next = update; } queue.pending = update; const alternate = fiber.alternate; if ( fiber === currentlyRenderingFiber || (alternate !== null && alternate === currentlyRenderingFiber) ) { // 渲染时更新,做好全局标记 didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true; } else { // fiber 的 updateQueue 为空,优化路径 // 下面这个 if 判断, 能保证当前创建的 update 是 queue.pending 中第一个 update // 因为发起更新之后 fiber.lanes 会被改动,如果 fiber.lanes 和 alternate.lanes 没有被改动,自然就是首个 update if ( fiber.lanes === NoLanes && (alternate === null || alternate.lanes === NoLanes) ) { const lastRenderedReducer = queue.lastRenderedReducer; if (lastRenderedReducer !== null) { let prevDispatcher; // 省略 try { const currentState: S = (queue.lastRenderedState: any); const eagerState = lastRenderedReducer(currentState, action); // 将 eagerState 和 eagerReducer 保存在 update 上 // 如果在 render 阶段 reducer == update.eagerReducer,则可以直接使用无需再次计算 update.eagerReducer = lastRenderedReducer; update.eagerState = eagerState; if (is(eagerState, currentState)) { // 无需调度更新 // 注:update 已经被添加到了 queue.pending,并没有丢弃。之后需要更新的时候,此 update 还是会起作用 return; } } catch (error) { } finally { // 省略 } } } // 发起调度更新 scheduleUpdateOnFiber(fiber, lane, eventTime); } if (enableSchedulingProfiler) { markStateUpdateScheduled(fiber, lane); } }
对于FunctionComponent,发起更新的方法是调用useState和useReducer返回的方法。 例如const [num, updateNum] = useState(0);,调用updateNum(1)就可以更新num。 其实useState只是useReducer的一种特殊情况,具体的放在后面hook的章节中讲,这里只要知道它们内部最终调用的是dispatchAction就行。 看一下dispatchAction,可以发现其实也是创建优先级变量 -> 创建 update 对象 -> update 加入 updateQueue -> 调度更新这个流程。
useState
useReducer
const [num, updateNum] = useState(0);
updateNum(1)
num
hook
dispatchAction
创建优先级变量 -> 创建 update 对象 -> update 加入 updateQueue -> 调度更新
从上面的分析中可以看到,触发三种类型组件更新的方法都有着相似的流程:
我们继续看看每一个步骤是怎么做的。
这个步骤比较统一,用的都是同一个方法requestUpdateLane:
requestUpdateLane
export function requestUpdateLane(fiber: Fiber): Lane { const mode = fiber.mode; if ((mode & BlockingMode) === NoMode) { // legacy 模式 return (SyncLane: Lane); } else if ((mode & ConcurrentMode) === NoMode) { // blocking 模式 return getCurrentPriorityLevel() === ImmediateSchedulerPriority ? (SyncLane: Lane) : (SyncBatchedLane: Lane); } else if ( !deferRenderPhaseUpdateToNextBatch && (executionContext & RenderContext) !== NoContext && workInProgressRootRenderLanes !== NoLanes ) { return pickArbitraryLane(workInProgressRootRenderLanes); } // concurrent 模式 if (currentEventWipLanes === NoLanes) { currentEventWipLanes = workInProgressRootIncludedLanes; } const isTransition = requestCurrentTransition() !== NoTransition; if (isTransition) { // 特殊情况,处于 suspense 过程中 if (currentEventPendingLanes !== NoLanes) { currentEventPendingLanes = mostRecentlyUpdatedRoot !== null ? mostRecentlyUpdatedRoot.pendingLanes : NoLanes; } return findTransitionLane(currentEventWipLanes, currentEventPendingLanes); } // 正常情况,获取调度优先级 const schedulerPriority = getCurrentPriorityLevel(); let lane; if ( (executionContext & DiscreteEventContext) !== NoContext && schedulerPriority === UserBlockingSchedulerPriority ) { // executionContext 存在输入事件,且调度优先级是用户阻塞性质 lane = findUpdateLane(InputDiscreteLanePriority, currentEventWipLanes); } else { // 调度优先级转换为车道模型 const schedulerLanePriority = schedulerPriorityToLanePriority( schedulerPriority, ); // 省略 lane = findUpdateLane(schedulerLanePriority, currentEventWipLanes); } return lane; }
在不同模式下会返回不同的优先级:
legacy模式
SyncLane
concurrent模式
在创建updata对象这一步,HostRoot和ClassComponent是一种结构,通过createUpdate这个函数创建;FunctionComponent是在dispatchAction函数中直接创建的,所以update是有两种结构的。
updata对象
createUpdate
update
export function createUpdate(eventTime: number, lane: Lane): Update<*> { const update: Update<*> = { eventTime, lane, tag: UpdateState, payload: null, callback: null, next: null, }; return update; }
function dispatchAction<S, A>( fiber: Fiber, queue: UpdateQueue<S, A>, action: A, ) { // 省略 // 创建 update对象 const update: Update<S, A> = { lane, action, eagerReducer: null, eagerState: null, next: (null: any), }; // 省略 }
上一步创建了update对象,这一步是将其加入updateQueue或者是hook.queue.pending中:
updateQueue
hook.queue.pending
export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) { const updateQueue = fiber.updateQueue; if (updateQueue === null) { return; } const sharedQueue: SharedQueue<State> = (updateQueue: any).shared; const pending = sharedQueue.pending; if (pending === null) { // 第一个 update update.next = update; } else { update.next = pending.next; pending.next = update; } sharedQueue.pending = update; // 省略 }
function dispatchAction<S, A>( fiber: Fiber, queue: UpdateQueue<S, A>, action: A, ) { // 省略 // 创建 update对象 const update: Update<S, A> = { lane, action, eagerReducer: null, eagerState: null, next: (null: any), }; // 将 update对象 添加到 hook.queue.pending 队列 const pending = queue.pending; if (pending === null) { // 首个 update,创建一个环形链表 update.next = update; } else { update.next = pending.next; pending.next = update; } queue.pending = update; // 省略 }
可以看到虽然一种是调用了enqueueUpdate方法,另一种是直接入队,但是逻辑都是一样的,updateQueue和hook.queue.pending都是环形链表,同时,为了方便入队和拿到队首元素,pending指向链表中最后一个元素。
enqueueUpdate
pending
最后就是发起调度更新了,这一步比较统一,都是调用scheduleUpdateOnFiber:
scheduleUpdateOnFiber
export function scheduleUpdateOnFiber( fiber: Fiber, lane: Lane, eventTime: number, ) { checkForNestedUpdates(); warnAboutRenderPhaseUpdatesInDEV(fiber); // 标记优先级 const root = markUpdateLaneFromFiberToRoot(fiber, lane); if (root === null) { warnAboutUpdateOnUnmountedFiberInDEV(fiber); return null; } markRootUpdated(root, lane, eventTime); if (root === workInProgressRoot) { if ( deferRenderPhaseUpdateToNextBatch || (executionContext & RenderContext) === NoContext ) { workInProgressRootUpdatedLanes = mergeLanes( workInProgressRootUpdatedLanes, lane, ); } if (workInProgressRootExitStatus === RootSuspendedWithDelay) { markRootSuspended(root, workInProgressRootRenderLanes); } } const priorityLevel = getCurrentPriorityLevel(); if (lane === SyncLane) { if ( (executionContext & LegacyUnbatchedContext) !== NoContext && (executionContext & (RenderContext | CommitContext)) === NoContext ) { schedulePendingInteractions(root, lane); // 首次渲染, 直接进行 fiber构造 performSyncWorkOnRoot(root); } else { // 对比更新,注册回调任务 ensureRootIsScheduled(root, eventTime); schedulePendingInteractions(root, lane); if (executionContext === NoContext) { resetRenderTimer(); // 如果执行上下文为空,取消 schedule 调度,主动刷新回调队列 flushSyncCallbackQueue(); } } } else { if ( (executionContext & DiscreteEventContext) !== NoContext && (priorityLevel === UserBlockingSchedulerPriority || priorityLevel === ImmediateSchedulerPriority) ) { if (rootsWithPendingDiscreteUpdates === null) { rootsWithPendingDiscreteUpdates = new Set([root]); } else { rootsWithPendingDiscreteUpdates.add(root); } } ensureRootIsScheduled(root, eventTime); schedulePendingInteractions(root, lane); } mostRecentlyUpdatedRoot = root; }
这个函数是承接输入的入口。渲染器可以不同,但是在react-reconciler中,想要改变fiber的操作,都会经过scheduleUpdateOnFiber,从此处开始就是进度任务调度流程了。
react-reconciler
fiber
一开始我们讲了整个渲染的简略流程,之后又梳理了几个关键节点,现在我们深入每个关键节点看看它们到底做了什么。
第一个节点是触发状态更新,之前举的例子是应用的启动,其实这只是触发状态更新的方式之一。 在 React 中,有以下几种触发状态更新的方法:
归类
看上去有好几种,实际上我们可以给他们归归类:
发起组件更新这一类型其实还可以根据组件类型分成两类:
ClassComponent
和FunctionComponent
,而应用初始化这种类型对应的组件是HostRoot
,所以以组件类型来划分的话,就是下面这样:(这么划分是因为后面的
update对象
有两种类型)虽然这几种方式看起来使用场景不同,实际上内部流程中有很多相似之处。
1.HostRoot
应用的启动中我们在最后讲到更新的时候会调用
updateContainer
这个函数:可以看到里面包含4个步骤:
调度更新
2.ClassComponent
我们再来看看
ClassComponent
的更新:里面有
enqueueSetState
、enqueueReplaceState
、enqueueForceUpdate
三个方法,从名字就可以看出对应setState
、replaceState
、forceUpdate
(enqueueReplaceState
是在内部用的,可以不管)。setState
和forceUpdate
的代码如下:Component.prototype.forceUpdate = function(callback) { this.updater.enqueueForceUpdate(this, callback, 'forceUpdate'); };
对于
FunctionComponent
,发起更新的方法是调用useState
和useReducer
返回的方法。 例如const [num, updateNum] = useState(0);
,调用updateNum(1)
就可以更新num
。 其实useState
只是useReducer
的一种特殊情况,具体的放在后面hook
的章节中讲,这里只要知道它们内部最终调用的是dispatchAction
就行。 看一下dispatchAction
,可以发现其实也是创建优先级变量 -> 创建 update 对象 -> update 加入 updateQueue -> 调度更新
这个流程。小结
从上面的分析中可以看到,触发三种类型组件更新的方法都有着相似的流程:
流程分析
我们继续看看每一个步骤是怎么做的。
1.创建一个优先级变量
这个步骤比较统一,用的都是同一个方法
requestUpdateLane
:在不同模式下会返回不同的优先级:
legacy模式
返回SyncLane
concurrent模式
正常情况下会根据调度优先级来生成一个车道优先级。2.根据优先级,创建 update 对象
在创建
updata对象
这一步,HostRoot
和ClassComponent
是一种结构,通过createUpdate
这个函数创建;FunctionComponent
是在dispatchAction
函数中直接创建的,所以update
是有两种结构的。3.将生成的 update 加入链表中
上一步创建了
update对象
,这一步是将其加入updateQueue
或者是hook.queue.pending
中:可以看到虽然一种是调用了
enqueueUpdate
方法,另一种是直接入队,但是逻辑都是一样的,updateQueue
和hook.queue.pending
都是环形链表,同时,为了方便入队和拿到队首元素,pending
指向链表中最后一个元素。4.调度更新
最后就是发起调度更新了,这一步比较统一,都是调用
scheduleUpdateOnFiber
:这个函数是承接输入的入口。渲染器可以不同,但是在
react-reconciler
中,想要改变fiber
的操作,都会经过scheduleUpdateOnFiber
,从此处开始就是进度任务调度流程了。