React 的源码流程
React 的源码架构分为三个部分:
调度器(Scheduler):调度任务的优先级,高优任务优先进入 Reconciler
协调器(Reconciler):VDOM 的实现,负责根据自变量变化计算出 UI 变化,简单的说就是找出变化的组件
渲染器(Renderer):负责将变化的组件渲染到页面上
同步更新流程(shouldTimeSlice 为 false)
import { createRoot } from 'react-dom'
const container = document.getElementById('app')
const root = createRoot(container)
root.render(<App />)
ReactDOM.createRoot
// 参数:
// container:DOM 元素,React 组件树将在其中进行渲染。
// options:可选配置对象,包括如何处理 Hydration 等。
function createRoot(container, options) {
var hydrate = options != null && options.hydrate === true
var hydrationCallbacks = (options != null && options.hydrationOptions) || null
var isStrictMode = options != null && options.unstable_strictMode === true
// 生成 FiberRootNode 和 HostRootFiber
var root = createContainer(container, ConcurrentRoot, hydrate, hydrationCallbacks, isStrictMode)
return new ReactDOMRoot(root)
}
/**
* FiberRootNode: {
* tag: ConcurrentRoot (就是 1)
* current: FiberNode(具体为 HostRootFiber)
* containerInfo: DOMElement(即为 createRoot 传入的 container 元素,例如 div#root)
* }
*
* FiberRootNode.current 为 HostRootFiber
* HostRootFiber: {
* tag: HostRoot (就是 3)
* stateNode: FiberRootNode (指向 FiberRootNode)
* }
*/
ReactDOM.createRoot(container, options?)
创建一个与 DOM 容器节点绑定的 root,称为 FiberRootNode。这个 root 负责管理 React 组件树的渲染。 返回值为 new ReactDOMRoot(root)
function ReactDOMRoot(internalRoot) {
this._internalRoot = internalRoot
}
ReactDOMRoot
实例上有 render
方法,用于将 React 元素渲染到容器中,当项目中调用 root.render(<App />)
时,这标志着渲染操作的开始。
//参数:
// children:React元素,通常是应用的顶层组件。
ReactDOMRoot.prototype.render = function (children) {
const root = this._internalRoot
var container = root.containerInfo
updateContainer(children, root, null, null)
}
render 阶段
Reconciler 工作的阶段在 React 内部被称为 render 阶段。
updateContainer
updateContainer(element, container, parentComponent, callback?)
在指定的 container 中渲染或更新给定的 React 元素。
创建或更新 Fiber 节点。
调用 schedulerUpdateOnFiber 以调度这个 Fiber 节点的更新。
scheduleUpdateOnFiber
scheduleUpdateOnFiber(fiber, lane, eventTime),标记 Fiber 节点上的更新,并根据更新的优先级(lanes)安排执行。
计算更新的优先级,确定更新所在的 lane。
根据 Fiber 节点的优先级,调用 ensureRootIsScheduled 来确保根节点的任务被调度。
ensureRootIsScheduled
ensureRootIsScheduled(root) 确保整个应用的根节点(root)有一个调度任务。
检查 root 上是否有待执行的工作。
根据当前的调度优先级,调用 scheduler 的 schedulerCallback 进行任务调度。
var schedulerPriorityLevel
switch (lanesToEventPriority(nextLanes)) {
case DiscreteEventPriority:
schedulerPriorityLevel = ImmediatePriority
break
case ContinuousEventPriority:
schedulerPriorityLevel = UserBlockingPriority
break
case DefaultEventPriority:
schedulerPriorityLevel = NormalPriority
break
case IdleEventPriority:
schedulerPriorityLevel = IdlePriority
break
default:
schedulerPriorityLevel = NormalPriority
break
}
scheduleCallback
scheduleCallback(priorityLevel, callback)
根据给定的优先级,安排一个回调函数在合适的时间执行。scheduleCallback 实际上是调用了 Scheduler.unstable_scheduleCallback var scheduleCallback = Scheduler.unstable_scheduleCallback
function unstable_scheduleCallback(priorityLevel, callback, options) {
var currentTime = exports.unstable_now()
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 ImmediatePriority:
timeout = IMMEDIATE_PRIORITY_TIMEOUT
break
case UserBlockingPriority:
timeout = USER_BLOCKING_PRIORITY_TIMEOUT
break
case IdlePriority:
timeout = IDLE_PRIORITY_TIMEOUT
break
case LowPriority:
timeout = LOW_PRIORITY_TIMEOUT
break
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) {
// This is a delayed task.
newTask.sortIndex = startTime
push(timerQueue, newTask)
if (peek(taskQueue) === null && newTask === peek(timerQueue)) {
// All tasks are delayed, and this is the task with the earliest delay.
if (isHostTimeoutScheduled) {
// Cancel an existing timeout.
cancelHostTimeout()
} else {
isHostTimeoutScheduled = true
} // Schedule a timeout.
requestHostTimeout(handleTimeout, startTime - currentTime)
}
} else {
newTask.sortIndex = expirationTime
push(taskQueue, newTask)
// wait until the next time we yield.
if (!isHostCallbackScheduled && !isPerformingWork) {
isHostCallbackScheduled = true
requestHostCallback(flushWork)
}
}
return newTask
}
requestHostCallback 调用 schedulePerformWorkUntilDeadline
在浏览器中使用 postMessage 技术确保尽早插入新的宏任务。
var channel = new MessageChannel()
var port = channel.port2
channel.port1.onmessage = performWorkUntilDeadline
schedulePerformWorkUntilDeadline = function () {
port.postMessage(null)
}
performWorkUntilDeadline 调用 flushWork 清空所有工作队列,执行所有剩余的工作。
flushWork 调用 workLoop 处理所有的工作单元,直到队列清空或达到浏览器的帧限制。
workLoop 调用 并发地执行根节点上的工作。
利用 React 的 Fiber 架构并发地执行更新,允许中断和恢复。
判断 shouldTimeSlice 为 false,调用 renderRootSync 同步渲染根节点。同步遍历 Fiber 树,执行更新,不允许任务中断。renderRootSync 会调用 workLoopSync。
判断 shouldTimeSlice 为 true,调用 renderRootConcurrent 异步渲染根节点。异步遍历 Fiber 树,执行更新,允许任务中断。renderRootConcurrent 会调用 workLoopConcurrent。
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress)
}
}
function workLoopConcurrent() {
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress)
}
}
renderRootSync 调用 workLoopSync 同步执行工作循环。
workLoopSync 调用 performUnitOfWork
performUnitOfWork 方法会创建下一个 fiberNode 并赋值给 workInProgress,并将 workInProgress 与已创建的 fiberNode 连接起来构成 Fiber Tree。
performUnitOfWork 中会调用 beginWork 和 completeWork,分别处理开始和结束工作。在遍历的过程中,会为 FiberNode 标记“代表不同副作用的 flags”,以便后续在 Renderer 中使用。
beginWork
beginWork 首先判断当前流程属于 mount 还是 update,判断依据为 current Fiber 是否存在(current !== null
)。
如果当前流程为 update 流程,则 workInProgress FiberNode 存在对应的 Current FiberNode(current = workInProgress.alternate
)。如果本次更新不影响 FiberNode.child,则可以复用对应的 Current FiberNode(attemptEarlyBailoutIfNoScheduledUpdate
),这是一条 render 阶段的优化路径。
如果无法复用 Current FiberNode,则 mount 和 update 的流程大体一致,包括:
根据 workInProgress.tag 进入“不同类型元素的处理分支”
HostComponent 代表原生 Element 类型(比如 DIV、SPAN)
IndeterminateComponent 是 FC mount 时进入的分支,update 时则进入 FunctionComponent 分支
HostText 代表文本元素类型
使用 reconcile 算法生成下一级 fiberNode
如果常见类型(比如 FunctionComponent、ClassComponent、HostComponent)没有命中优化策略,它们最终会调用 reconcileChildFibers 函数生成子 fiberNode
beginWork 的 mount 和 update 的区别在于:最终是否会为“生成的子 fiberNode”标记“副作用 flags”
reconcileChildFibers
reconcileChildFibers 负责协调(或者说更新)子 Fiber,它对比现有子 Fiber 节点与新生成的 React 元素之间的差异,决定子 Fiber 节点中哪些节点需要更新、删除或者创建,在这个方法中只是对子 Fiber 节点添加了 returnFiber.flags |= ChildDeletion
或 newFiber.flags |= Placement
这些标记。
completeWork
completeWork 具体逻辑如下:
根据 workInProgress.tag 进入不同的分支
在每个分支中,根据
current !== null
区分 mount 和 update 流程完成 mount 或 update 流程后,都会执行 bubbleProperties 方法完成 flags 的冒泡
completeWork 的 mount 流程
例如对于 HostComponent,在 mount 流程中首先会调用 createInstance 方法创建对应的 DOM 元素
然后执行
appendAllChildren
将下一级 DOM 元素挂在在上一步创建的 DOM 元素下执行
finalizeInitialChildren
完成属性的初始化styles,对应
setValueForStyles
方法innerHTML,对应
setInnerHTML
方法文本类型 children,对应
setTextContent
方法不会在 DOM 中冒泡的事件,包括 cancel、close、invalid、load、scroll、toggle,对应
listenToNonDelegatedEvent
方法其他属性,对应
setValueForProperty
方法
completeWork 的 update 流程
例如对于 HostComponent,在 update 流程中首先会调用 updateHostComponent 方法,主要是标记属性的更新
如果 oldProps 和 newProps 相等,会直接 return,如果不相等,会调用
diffProperties
方法diffProperties
方法包含两次遍历:第一次遍历:标记删除“更新前有,更新后没有”的属性
第二次遍历:标记更新“update 流程前后发生改变”的属性
所有变化属性的 key,value 会保存在 fiberNode.updateQueue 中,同时会标记
fiberNode.flags |= Update
beginWork 的 reconcileChildFibers 方法用来“标记 fiberNode 的插入、删除、移动”,completeWork 方法才是真正的将 UI 的变化整理出来,例如 HostComponent 的 mount 过程,会将生成的 DOM 元素放到 workInProgress.stateNode 然后挂在到父 DOM 上,update 过程会将属性的变化放到 updateQueue 中。
completeWork 的上层 completeUnitOfWork
completeUnitOfWork 中除了调用 completeWork 处理当前节点外,在最后会执行一下代码:
// beginWork 执行完,next 为 null 时,进入 completeWork,completeWork 执行完,会检查是否有 sibling
var siblingFiber = completedWork.sibling
// 如果有 siblingFiber,将 workInProgress 指向 siblingFiber,重新执行 performUnitOfWork
if (siblingFiber !== null) {
workInProgress = siblingFiber
return
}
// 如果没有 siblingFiber ,就进行 returnFiber 的 completeWork
completedWork = returnFiber
workInProgress = completedWork
commit 阶段
Renderer(渲染器)工作的阶段被称为 commit 阶段。在 commit 阶段,会将各种副作用(flags)commit(提交) 到宿主环境的 UI 中。
render 阶段流程可能会被打断,而 commit 阶段一旦开始就会同步执行直到完成
commit 工作流程分为三个阶段:
开始前的准备工作,比如判断“是否有副作用需要执行”
处理副作用
结束后的收尾工作,比如调度新的更新
处理副作用阶段还可以分为三个子阶段:
BeforeMutation 阶段
Mutation 阶段
Layout 阶段
commit 阶段的起点开始与 commitRoot 方法的调用。
那么 commitRoot 方法在什么时候调用?
函数会调用 renderRootSync 或 renderRootConcurrent,最后会调用 finishConcurrentRender 函数。
renderRootSync 或 renderRootConcurrent 中调用 completeUnitOfWork 的最后,会设置 workInProgressRootExitStatus = RootCompleted
,表示整个 workInProgress Fiber Tree 的构建完成。
在 finishConcurrentRender 函数中判断 exitStatus 等于 RootCompleted,会调用 commitRoot 函数。
commitRoot
commmitRoot(root) 的参数:
root 代表“本次更新所属的 FiberRootNode”
root.finishedWork 代表 workInProgress HostRootFiber,即“render 阶段构建的 wokeInProgress Fiber Tree” 的 HostRootFiber
在三个子阶段执行之前,需要判断本次更新是否涉及“与三个子阶段相关的副作用” 逻辑如下:
function commitRootImpl(root, renderPriorityLevel) {
// 省略之前代码
var finishedWork = root.finishedWork
// subtreeHasEffects 代表 workInProgress HostRootFiber 的子孙元素存在的副作用 flags
var subtreeHasEffects = (finishedWork.subtreeFlags & (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !== NoFlags
// rootHasEffect 代表 workInProgress HostRootFiber 自身存在的副作用 flags
var rootHasEffect = (finishedWork.flags & (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !== NoFlags
if (subtreeHasEffects || rootHasEffect) {
// 本次更新存在与三个子阶段相关的副作用
// 进入 commit 阶段的三个子阶段
var shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(root, finishedWork)
commitMutationEffects(root, finishedWork, lanes)
commitLayoutEffects(finishedWork, root, lanes)
} else {
// 省略:本次更新没有三个子阶段的副作用
}
}
mask 参考
Update:
ClassComponent 存在更新,且定义了 componentDidMount 或 componentDidUpdate 方法;
HostComponent 发生了属性变化
HostText 发生了内容变化
FC 定义了 useLayoutEffect
Snapshot: ClassComponet 存在更新,且定义了 getSnapshotBeforeUpdate 方法
Placement:当前 fiberNode 或子孙 fiberNode 存在“需要插入或移动的 HostComponent”
ChildDeletion:当前 fiberNode 或子孙 fiberNode 存在“需要删除的子 HostComponent 或子 HostText”
ContentReset:清空 HostComponent 的文本内容
Ref:HostComponent ref 属性的创建与更新
Hydrating: hydrate 相关
Visibility: 控制 SuspenseComponent 的子树与 fallback 切换时子树的显隐
Callback: 当 ClassComponent 中的 this.setState 执行时,或 ReactDOM.render 执行时传递了回调函数参数
Passive:FC 中定义了 useEffect 且需要触发回调函数
例如:
BeforeMutationMask: Update | Snapshot | 0,代表 BeforeMutation 子阶段跟 Update 和Snapshot 相关。由于 Snapshot 是“ClassComponet 存在更新,且定义了getSnapshotBeforeUpdate 方法”后才会设置的 flags,因此可以判断 BeforeMutation 子阶段会执行 getSnapshotBeforeUpdate 方法。
commitBeforeMutationEffects
commitBeforeMutationEffects 会调用 commitBeforeMutationEffects_begin
function commitBeforeMutationEffects(root, firstChild) {
// 省略之前代码
nextEffect = firstChild
commitBeforeMutationEffects_begin()
// 省略重置全局变量
}
commitBeforeMutationEffects_begin 会调用 commitBeforeMutationEffects_complete
function commitBeforeMutationEffects_begin() {
// 向下遍历直到“第一个满足如下条件之一的 fiberNode”
// 1. 当前 fiberNode 的子 fiberNode 不包含“该子阶段对应的 flags”,即当前 fiberNode 是“包含该子阶段对应的 flags”的“层级最低”的 fiberNode
while (nextEffect !== null) {
var fiber = nextEffect
var child = fiber.child
if ((fiber.subtreeFlags & BeforeMutationMask) !== NoFlags && child !== null) {
// 省略代码
nextEffect = child
} else {
commitBeforeMutationEffects_complete()
}
}
}
// commitBeforeMutationEffects_complete 用来执行 “flags 对应的操作”
function commitBeforeMutationEffects_complete() {
while (nextEffect !== null) {
var fiber = nextEffect
setCurrentFiber(fiber)
try {
// 对当前 fiberNode 执行“flags 对应的操作”
commitBeforeMutationEffectsOnFiber(fiber)
} catch (error) {
// 错误处理
captureCommitPhaseError(fiber, fiber.return, error)
}
resetCurrentFiber()
var sibling = fiber.sibling
if (sibling !== null) {
// 如果当前 fiberNode 存在兄弟 fiberNode,nextEffect 赋值为 sibling,再次走到commitBeforeMutationEffects_begin的 while 循环中
nextEffect = sibling
return
}
// 如果当前 fiberNode 不存在兄弟 fiberNode,nextEffect 赋值为 父 fiberNode,再次走到commitBeforeMutationEffects_begin的 while 循环中
nextEffect = fiber.return
}
}
以上可以看出,如果说 render 阶段的 completeWork 会完成“自下而上”的 subtreeFlags 标记过程,那么 commit 阶段的 commitBeforeMutationEffects 会完成“自下而上”的 subtreeFlags 消费过程。
commitBeforeMutationEffectsOnFiber
主要处理一下两种类型的 fiberNode:
ClassComponent 执行 getSnapshotBeforeUpdate 方法
HostRoot 清空 HostRoot 挂载的内容,方便 Mutation 子阶段的渲染
commitMutationEffects
commitMutationEffects 跟 commitBeforeMutationEffects 的逻辑类似:
commitMutationEffects 会调用 commitMutationEffects_begin
commitMutationEffects_begin 会调用 commitMutationEffects_complete
commitMutationEffects_begin 有特有的 commitDeletion 逻辑,用来处理“需要删除的 fiberNode”,需要考虑的事情很多:子树中所有组件的 ummout 逻辑,子树中所有 ref 属性的卸载操作,子树中所有 effect 相关 Hook 的 destory 回调执行等
commitMutationEffectsOnFiber
Placement flag 对应 commitPlacement 方法,执行流程分为三个步骤
从当前 fiberNode 向上遍历,获取第一个类型为 HostComponent、HostRoot、HostPortal 三者之一的祖先 fiberNode,其对应的 DOM 元素是“执行 DOM 操作的目标元素的父级 DOM 元素”
var parentFiber = getHostParentFiber(finishedWork)
获取用于执行 parentNode.insertBefore(child, before) 方法的 “before 对应 DOM 元素”
var before = getHostSibling(finishedWork)
执行 parentNode.insertBefore 方法(存在 before 的情况)或 parentNode.appendChild 方法(不存在 before 的情况)
Update flag 对应 commitWork 方法
对于 FC 等类型的 fiberNode,会执行 commitHookEffectListUnmount 和 commitHookEffectListMount 方法
对于 HostComponent 类型的 fiberNode,会执行 commitUpdate 方法,commitUpdate 方法又会调用 updateProperties 最终会在 updateDomProperties 方法中遍历并改变对应属性,处理以下四种类型的数据
style 属性变化
innerHTML
直接文本节点变化
其他元素属性
commitLayoutEffects
commitLayoutEffects 跟 commitBeforeMutationEffects 的逻辑类似:
commitLayoutEffects 会调用 commitLayoutEffects_begin
commitLayoutEffects_begin 会调用 commitLayoutEffects_complete
Fiber Tree 切换
在进入 Layout 子阶段之前,会执行如下代码完成 Fiber Tree 的切换:
root.current = finishedWork
之所以选择这一时机切换 Fiber Tree,是因为对于 ClassComponent,当执行 componentWillUnMount 时(Mutation 子阶段),Current Fiber Tree 仍对应 UI 中的树。当执行 commponentDidMount/Update 时(Layout 子阶段),Current Fiber Tree 已经对应本次更新的 Fiber Tree。
useLayoutEffect callback 会在该阶段执行
commitLayoutEffectsOnFiber
对于 ClassComponent
会执行 componentDidMount 或 componentDidUpdate 方法
执行 this.setState(newState, callback) 传递的 callback 参数会保存在对应的 fiberNode.updateQueue 中,commitLayoutEffectsOnFiber 会执行这些 callback
对于 FC,会执行 useLayoutEffect callback
对于 HostRoot,执行 ReactDOM.render 传递的 callback 参数会保存在对应的 fiberNode.updateQueue 中,commitLayoutEffectsOnFiber 会执行这些 callback
Last updated
Was this helpful?