ReactDOM 渲染流程
创建更新的方式
- ReactDOM.render || hydrate
- setState
- forceUpdate
ReactDOM.render
步骤
- 创建ReactRoot
- 创建FiberRoot和RootFiber
- 创建更新
render源码流程
- render (ReactDOM.js)
-
legacyRenderSubtreeIntoContainer (ReactDOM.js)
-
legacyCreateRootFromDOMContainer (ReactDOM.js)
-
ReactRoot (ReactDOM.js)
-
createContainer (react-reconciler/inline.dom)
react-reconciler
操作与平台无关的节点调和以及操作任务调度 - createContainer (react-reconciler/inline.dom)
-
updateContainer (react-reconciler/inline.dom)
// expirationTime 优先级任务更新的重要字段 export function updateContainer( element: ReactNodeList, container: OpaqueRoot, parentComponent: ?React$Component<any, any>, callback: ?Function, ): ExpirationTime { const current = container.current; const currentTime = requestCurrentTime(); const expirationTime = computeExpirationForFiber(currentTime, current); return updateContainerAtExpirationTime( element, container, parentComponent, expirationTime, callback, ); }
- updateContainerAtExpirationTime (react-reconciler/inline.dom)
FiberRoot
作用
- 整个应用的起点
- 包含应用挂载的目标节点
- 记录整个应用更新过程的各种信息
数据结构
type BaseFiberRootProperties = {
// root既然点,render方法的第二个参数
containerInfo: any,
// 只有在持久化更新中会用到,也就是不支持增量更新的平台,react-dom不会用到
pendingChildren: any,
// 当前应用对应的Fiber对象,是Root Fiber
current: Fiber,
// 以下的优先级是用来区分
// 1) 没有提交(commited)的任务
// 2) 没有提交的挂起任务
// 3) 没有提交的可能被挂起的任务
// 我们选择不追踪每个单独的阻塞登记,为了兼顾性能
// 最老和最新的在提交的时候被挂起的任务
earliestSuspendedTime: ExpiretionTime,
latestSuspendedTime: ExpiretionTime,
// 最老和最新的不确定是否会挂起的优先级(所有任务一开始进来都是这个状态)
earliestPendingTime: ExpiretionTime,
latestPendingTime: ExpiretionTime,
// 最新的通过一个Promise被Resolve并且可以重新尝试的优先级
latestPingedTime: ExpiretionTime,
// 如果有错误被抛出并且没有更多的更新存在,我们尝试在处理错误前同步重新渲染
// 在renderRoot出现无法处理的错误时会被设置成true
didError: boolean,
// 正在等待提交的任务的expirationTime
pendingCommitExpirationTime: ExpiretionTime,
// 已经完成任务的Fiber对象,如果只有一个Root,那它只可能时这个Root对应的Fiber,或者是null
// 在commit阶段只会处理这个字段对应的任务
finishedWork: Fiber | null,
// 任务被挂起时通过setTimeout设置的返回内容,用来下一次如果有新任务挂起时清理还未触发的timeout
timeoutHandle: TimeoutHandle | NoTimeout,
// 顶层context对象,只有主动调用`renderSubtreeIntoContainer`时才会生效
context: Object | null,
pendingContext: Object | null,
// 确定第一次渲染时是否需要融合
hydrate: boolean,
// 当前root上剩余的过期时间
// TODO: 提到renderer里面去处理
nextExpirationTimeToWorkOn: ExpiretionTime,
// 当前更新对应的过期时间
expirationTime: ExpiretionTime,
// 顶层批次(批处理任务?) 这个变量知名一个commit是否应该被推迟
// 同时包括完成之后的回调
// 貌似在测试的时候?
firStBatch: Batch | null,
// root之间的链表结构
nextScheduleRoot: FiberRoot | null,
}
Fiber
作用
- 每个ReactElement对应一个Fiber对象
- 记录节点的各种状态
- 串联整个应用形成树结构
数据结构
type Fiber = {
// 标记不同的组件类型
tag: WorkTag,
// RecatElement里面的key
key: null | string,
// ReactElement.typr,也就是我们调用`createElement`的第一个参数
elementType: any,
// 异步组件resolved之后返回的内容,一般是 class | function
type: any,
// 跟当前Fiber相关的本地状态(比如浏览器环境就是DOM节点)
stateNode: any,
// 指向它在Fiber节点网中的`parent`,用来在处理完这个节点之后向上返回
return: Fiber | null,
// 指向自己的第一个子节点
child: Fiber | null,
// 指向自己的兄弟节点
sibling: Fiber | null,
index: number,
// ref属性
ref: null | (((handle: mixed) => void) & {_strinfRef: ?string}) | RefObject),
// 新的变动带来新的props
pendingProps: any,
// 上次渲染完成后的props
memoizeProps: any,
// 该Fiber对应的组件产生的Update队列
updateQueue: updateQueue<any> | null,
// 上次渲染时的state
memoizedState: any,
// 一个列表,存放这个Fiber依赖的context
firstContextDependency: ContextDependency<mixed> | null,
// 用来描述当前Fiber和其它子树的`Bitfield`
// 共存的模式表示这个子树是否默认是异步渲染的
// Fiber被创建的时候它会继承父Fiber
// 其他标识也可以在创建时被设置
// 但是在创建之后不应该在被修改,特别是在子Fiber创建之前
mode: TypeOfMode,
// 用来记录Side Effect
effectTag: SideEffectTag,
// 单链表用来快速查找下一个side effect
nextEffect: Fiber | null,
// 子树中第一个side effect
nextEffect: Fiber | null,
// 子树中最后一个side effect
lastEffect: Fiber | null,
// 过期时间
expirationTime: ExpirationTime,
ChildExpirationTime: ExpirationTime,
// `current <===> workInProgress` Double Buffer
// 存储一个Fiber,减少构建一个Fiber的性能开销
alternate: Fiber | null,
// 调试相关,收集每隔Fiber和子树的渲染时间
actualDuration?: number,
actualStartTime?: number,
selfBaseDuration?: number,
treeBaseDuration?: number,
_debugID?: number,
_debugSource?: Source | null,
_debugOwner?: Fiber | null,
_debugIsCurrentlyTiming?: boolean,
}
Update & UpdateQueue
作用
- 用于记录组件状态的改变
- 存放于UpdateQueue中
- 多个Update可以同时存在
数据结构
type Update<State> = {
// 更新的过期时间
expirationTime: ExpirationTime,
// 指定更新的类型
// UpdateState = 0
// ReplaceState = 1
// ForceUpdate = 2
// CaptureUpdate = 3 捕获的错误
tag: 0 | 1 | 2 | 3,
// 更新内容 比如`setState`的第一个参数
payload: any,
// 对应的回调,`setState` `render` 都有
callback: (() => mixed ) | null,
// 指向下一个更新
next: Update<State> | null,
// 指向下一个`side effect`
nextEffect: Update<State> | null,
}
type UpdateQueue<State> = {
// 每次操作完更新之后的`state`
baseState: State,
// 队列中的第一个/最后一个`Update`
firstUpdate: Update<State> | null,
lastUpdate: Update<State> | null,
//第一个/最后一个捕获类型的`Update`
firstCapturedUpdate: Update<State> | null,
lastCapturedUpdate: Update<State> | null,
// 第一个/最后一个`side effect`
firstEffect: Update<State> | null,
lastEffect: Update<State> | null,
// 第一个/最后一个捕获产生的`side effect`
firstCapturedEffect: Update<State> | null,
lastCapturedEffect: Update<State> | null,
}
expirationTime
计算过程
((((currentTime - 2 + 5000 / 10) / 25) | 0) + 1) * 25
类型
- Sync模式
优先级最高的模式,创建即更新
- 异步模式
-
指定context
setState & forceUpdate
核心
- 给节点的Fiber创建更新
setState & forceUpdate 如此,reactDOM.render初始化渲染,
- 更新的类型不同
tag对应更新类型不同
总体流程
步骤
- Scheduler的整体流程概述
- 调度过程中的全局变量一览
- 构建任务调度的概念
scheduleWork
步骤
- 找到更新对应的FiberRoot节点
- 如果符合条件重置stack
- 如果符合条件就请求工作调度
function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
const root = scheduleWorkToRoot(fiber, expirationTime);
if (root === null) {
return;
}
if (
!isWorking &&
nextRenderExpirationTime !== NoWork &&
expirationTime > nextRenderExpirationTime
) {
interruptedBy = fiber;
resetStack();
}
markPendingPriorityLevel(root, expirationTime);
if (
!isWorking ||
isCommitting ||
nextRoot !== root
) {
const rootExpirationTime = root.expirationTime;
requestWork(root, rootExpirationTime);
}
// nestedUpdateCount 防止生命周期内进行状态更新,进行标记
if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
nestedUpdateCount = 0;
invariant(
false,
'Maximum update depth exceeded. This can happen when a ' +
'component repeatedly calls setState inside ' +
'componentWillUpdate or componentDidUpdate. React limits ' +
'the number of nested updates to prevent infinite loops.',
);
}
}
requestWork
核心
- 加入到root调度队列
- 判断是否批量更新
- 更具expirationTime判断调度类型
function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
addRootToSchedule(root, expirationTime);
if (isRendering) {
return;
}
// 批处理
if (isBatchingUpdates) {
if (isUnbatchingUpdates) {
nextFlushedRoot = root;
nextFlushedExpirationTime = Sync;
performWorkOnRoot(root, Sync, false);
}
return;
}
if (expirationTime === Sync) {
performSyncWork();
} else {
scheduleCallbackWithExpirationTime(root, expirationTime);
}
}
流程未走完,更新见下节