大概流程
先前说到React应用在首次启动之后会形成如下的结构:
在此之后 render 函数被执行进入如下过程,这里我们省略调度器scheduler内的具体流程,只需要了解调度器的作用就是传入一个回调函数,调度器会决定在合适的时机去执行这个回调函数即可;
Fiber树初次构造过程
React项目不管是首次渲染还是后续的渲染过程都会经过scheduleUpdateOnFiber,我们省略函数中关于DEV模式以及渲染过程中的一些额外处理:
export function scheduleUpdateOnFiber(
root: FiberRoot, //根节点容器
fiber: Fiber, // 当前Fiber节点
lane: Lane, // 优先级
) {
markRootUpdated(root, lane);
ensureRootIsScheduled(root);
}
可以看到其中主要做了两个操作:
- 标记根 Fiber 有待处理的更新
- 确保根 Fiber 被调度
1.注册调度任务
上述过程中为了确保Fiber被调度,ensureRootIsScheduled函数被调用:
export function ensureRootIsScheduled(root: FiberRoot): void {
// 将根 Fiber 添加到调度列表
if (root === lastScheduledRoot || root.next !== null) {
// 这个根 Fiber 已经被调度过了。
} else {
// 如果 lastScheduledRoot 为空,初始化第一个和最后一个调度的根 Fiber
if (lastScheduledRoot === null) {
firstScheduledRoot = lastScheduledRoot = root;
} else {
// 将当前最后一个调度的根 Fiber 的 next 指向新的根 Fiber
// 并更新 lastScheduledRoot 为新的根 Fiber
lastScheduledRoot.next = root;
lastScheduledRoot = root;
}
}
// 标记可能有同步工作待处理
mightHavePendingSyncWork = true;
// 调度一个微任务来处理根调度列表
if (!didScheduleMicrotask) {
didScheduleMicrotask = true;
scheduleImmediateTask(processRootScheduleInMicrotask);
}
}
函数做了如下的操作:
- 确保根 Fiber 在根调度列表中。
- 确保有一个待处理的微任务来处理根调度列表。
后续实际的调度逻辑会在 scheduleTaskForRootDuringMicrotask 中进行。
function scheduleTaskForRootDuringMicrotask(
root: FiberRoot,
currentTime: number,
): Lane {
// ...省略处理优先级的逻辑
// 如果有工作要做,并且根 Fiber 不是因为等待数据解析而挂起
if (
nextLanes !== NoLanes &&
!(root === getWorkInProgressRoot() && isWorkLoopSuspendedOnData()) &&
root.cancelPendingCommit === null
) {
// 使用最高优先级车道作为回调的优先级
const newCallbackPriority = getHighestPriorityLane(nextLanes);
// 安排一个新的回调任务
const newCallbackNode = scheduleCallback(
// 根据车道的优先级确定调度器的优先级级别
lanesToEventPriority(nextLanes) === DefaultEventPriority
? NormalSchedulerPriority
: UserBlockingSchedulerPriority,
// 这是回调执行时绑定 root 的函数
performWorkOnRootViaSchedulerTask.bind(null, root),
);
// 更新根 Fiber 的回调信息
root.callbackPriority = newCallbackPriority;
root.callbackNode = newCallbackNode;
return newCallbackPriority;
}
// ...之后的逻辑
}
函数做了如下操作:
-
进行调度优先级的判断;
-
判断是否需要注册新的调度:如果不需要则取消任何现有的回调任务,并返回表示没有车道需要处理的
NoLane。 -
需要注册新的调度的情况:使用
scheduleCallback函数在宿主环境中调度一个新的回调任务。这个回调任务会在稍后执行,以便处理根 Fiber 的更新。 -
更新根 Fiber 的
callbackNode和callbackPriority,以便跟踪当前调度的回调任务和它的优先级。
经过处理后具体任务的执行被放在了scheduleCallback中用于安排回调。
2.执行任务回调
通过调用堆栈可以看到 当
scheduleCallback被执行之后就进入了scheduler模块之中,我们暂时省略调度相关过程,来看看调度完成后如何执行任务回调,上面的过程经过回调之后经过一系列调用,最后
performConcurrentWorkOnRoot会被调用
function performWorkOnRootViaSchedulerTask(
root: FiberRoot,
didTimeout: boolean,
): RenderTaskFn | null {
// 确定接下来要处理的车道。
const workInProgressRoot = getWorkInProgressRoot();
const workInProgressRootRenderLanes = getWorkInProgressRootRenderLanes();
// 获取本次渲染的优先级
const lanes = getNextLanes(
root,
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
);
if (lanes === NoLanes) {
// 这个根上没有更多的工作要做。
return null;
}
// 进入工作循环。
performWorkOnRoot(root, lanes, forceSync);
}
performWorkOnRoot:
export function performWorkOnRoot(
root: FiberRoot,
lanes: Lanes,
forceSync: boolean,
): void {
// 确定是否应该对工作进行时间切片(time-slicing)。
// 时间切片在某些情况下会被禁用:如果工作已经在CPU上运行了太长时间(“过期”的工作,以防止饿死),或者我们在默认同步更新模式下。
const shouldTimeSlice =
(!forceSync &&
!includesBlockingLane(lanes) &&
!includesExpiredLane(root, lanes)) ||
(enableSiblingPrerendering && checkIfRootIsPrerendering(root, lanes));
// 根据是否应该时间切片,选择渲染方式。
let exitStatus = shouldTimeSlice
? renderRootConcurrent(root, lanes) // 并发渲染
: renderRootSync(root, lanes, true); // 同步渲染
// 工作循环,直到完成或需要挂起。
do {
let renderWasConcurrent = shouldTimeSlice;
if (exitStatus === RootInProgress) {
// 渲染阶段仍在进行中。
if (
enableSiblingPrerendering &&
workInProgressRootIsPrerendering &&
!shouldTimeSlice
) {
// 我们在预渲染模式下,但是没有启用时间切片。这发生在同步更新期间某些东西挂起时。
// 退出工作循环。当我们恢复时,我们将使用并发工作循环,以便预渲染不阻塞主线程。
const didAttemptEntireTree = false;
markRootSuspended(root, lanes, NoLane, didAttemptEntireTree);
}
break;
} else if (exitStatus === RootDidNotComplete) {
// 渲染没有完成树的构建。
const didAttemptEntireTree = !workInProgressRootDidSkipSuspendedSiblings;
markRootSuspended(root, lanes, NoLane, didAttemptEntireTree);
} else {
// 渲染完成了。
// 检查这次渲染是否可能已经让步给了并发事件,如果是的话,确认任何新渲染的存储是一致的。
const finishedWork: Fiber = (root.current.alternate: any);
if (
renderWasConcurrent &&
!isRenderConsistentWithExternalStores(finishedWork)
) {
// 在交错的事件中修改了存储。再次同步渲染,以阻止进一步的修改。
exitStatus = renderRootSync(root, lanes, false);
renderWasConcurrent = false;
continue;
}
if (exitStatus === RootFatalErrored) {
prepareFreshStack(root, NoLanes);
const didAttemptEntireTree = true;
markRootSuspended(root, lanes, NoLane, didAttemptEntireTree);
break;
}
// 我们现在有了一个一致的树。下一步是提交它,或者如果某些东西挂起了,在超时后提交它。
finishConcurrentRender(root, exitStatus, finishedWork, lanes);
}
break;
} while (true);
// 确保根被调度。
ensureRootIsScheduled(root);
}
3.进入渲染循环
renderRootSync: 负责同步地执行根 Fiber 的渲染工作,直到完成或遇到错误。
// 同步渲染根组件的函数
function renderRootSync(
root: FiberRoot,
lanes: Lanes,
shouldYieldForPrerendering: boolean,
): RootExitStatus {
// 暂存上下文等内容
const prevExecutionContext = executionContext;
executionContext |= RenderContext;
const prevDispatcher = pushDispatcher(root.containerInfo);
const prevAsyncDispatcher = pushAsyncDispatcher();
// fiberRoot有变化 或者 update.lane变动 刷新栈
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
workInProgressTransitions = getTransitionsForLanes(root, lanes);
prepareFreshStack(root, lanes);
}
if (enableSchedulingProfiler) {
markRenderStarted(lanes); // 标记渲染开始
}
let didSuspendInShell = false; // 标记是否在壳层挂起
let exitStatus = workInProgressRootExitStatus; // 初始化退出状态
// 渲染循环
outer: do {
try {
workLoopSync(); // 执行同步工作循环
break;
} catch (thrownValue) {
handleThrow(root, thrownValue); // 处理抛出的异常
}
} while (true);
resetContextDependencies(); // 重置上下文依赖
executionContext = prevExecutionContext; // 恢复执行上下文
popDispatcher(prevDispatcher); // 恢复调度器
popAsyncDispatcher(prevAsyncDispatcher); // 恢复异步调度器
if (enableSchedulingProfiler) {
markRenderStopped(); // 标记渲染停止
}
if (workInProgress !== null) {
// 如果工作栈不为空,表示渲染未完成
} else {
// 渲染完成,重置对应的全局 变量
workInProgressRoot = null;
workInProgressRootRenderLanes = NoLanes;
finishQueueingConcurrentUpdates(); // 完成并发更新
}
return exitStatus; // 返回退出状态
}
函数中进行了如下操作:
调用prepareFreshStack刷新栈
// 准备一个新的工作栈,以便开始一个新的渲染周期
function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
// ...
// 重置workInProgress
resetWorkInProgressStack();
// 使用旧的hostFiber创建一个本次工作的WorkInProgress
const rootWorkInProgress = createWorkInProgress(root.current, null);
// 将全局的workInProgress 赋值为新的hostFiber节点
workInProgress = rootWorkInProgress;
// ...
// 返回新的WorkInProgress
return rootWorkInProgress;
}
函数主要调用了createWorkInProgress方法根据旧的hostFiber节点创建新的hostFiber节点,并且将新的节点赋值给了workInProgress
进入同步渲染工作循环workLoopSync:
function workLoopSync() {
// Perform work without checking if we need to yield between fiber.
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
workLoopSync中只做了一件事开启了一个循环,当workInProgress不为空的时候,会持续调用performUnitOfWork方法创建Fiber树,直到Fiber树创建完成;
performUnitOfWork:
// 执行一个工作单元(Fiber节点)的函数
function performUnitOfWork(unitOfWork: Fiber): void {
// 获取当前已提交的Fiber状态,即alternate
const current = unitOfWork.alternate;
// 初始化下一个工作单元的引用
let next;
next = beginWork(current, unitOfWork, entangledRenderLanes);
}
// 将pendingProps复制到memoizedProps,表示当前工作单元的props已经处理
unitOfWork.memoizedProps = unitOfWork.pendingProps;
// 如果beginWork没有返回新的工作(即没有新的子节点或兄弟节点需要处理)
if (next === null) {
// 完成当前工作单元的工作
completeUnitOfWork(unitOfWork);
} else {
// 如果有新的工作,将其设置为下一个工作单元
workInProgress = next;
}
}
beginWork:
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
// 如果当前 Fiber 节点存在,比较新旧属性和上下文是否发生变化。
if (current !== null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
// 如果属性、上下文或类型发生变化,标记为需要更新。
if (
oldProps !== newProps ||
hasLegacyContextChanged()
) {
didReceiveUpdate = true;
} else {
// 如果属性和上下文没有变化,检查是否有计划的更新或上下文变化以及错误等情况。
// ...代码省略
}
} else {
// 没有current表示这是一个新节点 不需要更新
didReceiveUpdate = false;
}
// 在进入开始阶段之前,清除挂起的更新优先级。
workInProgress.lanes = NoLanes;
// 根据 Fiber 节点的类型,执行不同的更新逻辑。
switch (workInProgress.tag) {
case LazyComponent: {
const elementType = workInProgress.elementType;
return mountLazyComponent(
current,
workInProgress,
elementType,
renderLanes,
);
}
case FunctionComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
disableDefaultPropsExceptForClasses ||
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultPropsOnNonClassComponent(Component, unresolvedProps);
return updateFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
case ClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps = resolveClassComponentProps(
Component,
unresolvedProps,
workInProgress.elementType === Component,
);
return updateClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
case HostRoot:
return updateHostRoot(current, workInProgress, renderLanes);
case HostHoistable:
if (supportsResources) {
return updateHostHoistable(current, workInProgress, renderLanes);
}
// Fall through
case HostSingleton:
if (supportsSingletons) {
return updateHostSingleton(current, workInProgress, renderLanes);
}
// Fall through
case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes);
case HostText:
return updateHostText(current, workInProgress);
case SuspenseComponent:
return updateSuspenseComponent(current, workInProgress, renderLanes);
case HostPortal:
return updatePortalComponent(current, workInProgress, renderLanes);
// ...这里省略多个case
}
beginWork 在Fiber节点向下遍历时进行, 内部逻辑主要是根据不同的节点类型去调用对应的函数处理节点的更新;
在首次的进入beginWork时节点tag是HostRoot会进入updateHostRoot函数中执行:
// 更新根主机组件(通常是 div 元素)的函数
function updateHostRoot(
current: null | Fiber,
workInProgress: Fiber,
renderLanes: Lanes,
) {
// 获取待处理的props和状态
const nextProps = workInProgress.pendingProps;
const prevState = workInProgress.memoizedState;
const prevChildren = prevState.element;
// 克隆更新队列
cloneUpdateQueue(current, workInProgress);
// 处理更新队列
processUpdateQueue(workInProgress, nextProps, null, renderLanes);
// 获取处理后的state
const nextState: RootState = workInProgress.memoizedState;
const root: FiberRoot = workInProgress.stateNode;
// 获取处理后的子元素
const nextChildren = nextState.element;
// 如果子元素没有变化,跳过本次工作
if (nextChildren === prevChildren) {
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
}
// 协调子元素
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
// 返回第一个子元素
return workInProgress.child;
}
当子节点无法复用的情况下,会调用reconcileChildren进入节点的reconcile流程,处理更新的子节点的内容
function reconcileChildren(
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any,
renderLanes: Lanes,
) {
// 当前节点是否是新创建的
if (current === null) {
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderLanes,
);
} else {
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
nextChildren,
renderLanes,
);
}
}
对于HostRoot节点来说current在应用初始化的过程中就已经创建了,所以后直接进入到reconcileChildFibers
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
lanes: Lanes,
): Fiber | null {
const firstChildFiber = reconcileChildFibersImpl(
returnFiber,
currentFirstChild,
newChild,
lanes,
);
return firstChildFiber;
}
reconcileChildFibersImpl被调用
// 协调子 Fiber 节点的实现函数
function reconcileChildFibersImpl(
returnFiber: Fiber, // 父级 Fiber 节点
currentFirstChild: Fiber | null, // 当前的子 Fiber 节点
newChild: any, // 新的子节点
lanes: Lanes, // 渲染的优先级车道
): Fiber | null {
// 处理对象类型的子节点
if (typeof newChild === 'object' && newChild !== null) {
// 处理 React 元素类型
if (newChild.$$typeof === REACT_ELEMENT_TYPE) {
const firstChild = placeSingleChild(
reconcileSingleElement(
returnFiber,
currentFirstChild,
newChild,
lanes,
),
);
return firstChild;
}
// 处理 React Portal 类型
if (newChild.$$typeof === REACT_PORTAL_TYPE) {
return placeSingleChild(
reconcileSinglePortal(
returnFiber,
currentFirstChild,
newChild,
lanes,
),
);
}
// 处理 React 懒加载类型
if (newChild.$$typeof === REACT_LAZY_TYPE) {
// 省略部分代码....
return firstChild;
}
// 处理数组类型的子节点
if (isArray(newChild)) {
// 省略部分代码....
return firstChild;
}
// 处理可迭代类型的子节点
if (getIteratorFn(newChild)) {
// 省略部分代码....
return firstChild;
}
// 处理异步可迭代类型的子节点
if (
enableAsyncIterableChildren &&
typeof newChild[ASYNC_ITERATOR] === 'function'
) {
// 省略部分代码....
return firstChild;
}
// 处理 Promise 类型的子节点
if (typeof newChild.then === 'function') {
// 省略部分代码....
return firstChild;
}
// 处理 Context 类型的子节点
if (newChild.$$typeof === REACT_CONTEXT_TYPE) {
// 省略部分代码....
}
// 处理字符串、数字或 BigInt 类型的子节点
if (
(typeof newChild === 'string' && newChild !== '') ||
typeof newChild === 'number' ||
typeof newChild === 'bigint'
) {
return placeSingleChild(
reconcileSingleTextNode(
returnFiber,
currentFirstChild,
// 将子节点转换为字符串
'' + newChild,
lanes,
),
);
}
// 处理其他情况,视为空
return deleteRemainingChildren(returnFiber, currentFirstChild);
}
这里根据新子节点的类型类型做了不同的处理,我们先来看最常见的类型REACT_ELEMENT_TYPE(包括类组件、函数组件、DOM节点等)的处理。
reconcileSingleElement
// 协调单个 React 元素的函数
function reconcileSingleElement(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
element: ReactElement,
lanes: Lanes,
): Fiber {
const key = element.key; // 获取元素的 key
let child = currentFirstChild; // 获取当前的第一个子 Fiber 节点
while (child !== null) {
// 如果子 Fiber 节点的 key 与新元素的 key 相同 做更新处理
if (child.key === key) {
const elementType = element.type; // 获取元素的类型
// 如果元素类型是 Fragment
if (elementType === REACT_FRAGMENT_TYPE) {
if (child.tag === Fragment) {
// 删除剩余的子节点
deleteRemainingChildren(returnFiber, child.sibling);
// 复用现有的 Fiber 节点
const existing = useFiber(child, element.props.children);
existing.return = returnFiber;
validateFragmentProps(element, existing, returnFiber);
return existing;
}
}
break;
} else {
// 删除子节点
deleteChild(returnFiber, child);
}
child = child.sibling; // 移动到下一个兄弟节点
}
// 如果元素类型是 Fragment
if (element.type === REACT_FRAGMENT_TYPE) {
// 创建新的 Fiber 节点
const created = createFiberFromFragment(
element.props.children,
returnFiber.mode,
lanes,
element.key,
);
created.return = returnFiber;
validateFragmentProps(element, created, returnFiber);
return created;
} else {
// 创建新的 Fiber 节点
const created = createFiberFromElement(element, returnFiber.mode, lanes);
coerceRef(returnFiber, currentFirstChild, created, element);
created.return = returnFiber;
return created;
}
}
之后流程会进入createFiberFromElement创建Fiber节点:
function createFiberFromElement(
element: ReactElement,
mode: TypeOfMode,
lanes: Lanes,
): Fiber {
let owner = null;
const type = element.type;
const key = element.key;
const pendingProps = element.props;
const fiber = createFiberFromTypeAndProps(
type,
key,
pendingProps,
owner,
mode,
lanes,
);
return fiber;
}
createFiberFromTypeAndProps
export function createFiberFromTypeAndProps(
type: any, // React$ElementType
key: null | string,
pendingProps: any,
owner: null | ReactComponentInfo | Fiber,
mode: TypeOfMode,
lanes: Lanes,
): Fiber {
let fiberTag = FunctionComponent;
let resolvedType = type;
if (typeof type === 'function') {
if (shouldConstruct(type)) {
fiberTag = ClassComponent;
}
} else if (typeof type === 'string') {
if (supportsResources && supportsSingletons) {
const hostContext = getHostContext();
fiberTag = isHostHoistableType(type, pendingProps, hostContext)
? HostHoistable
: isHostSingletonType(type)
? HostSingleton
: HostComponent;
} else if (supportsResources) {
const hostContext = getHostContext();
fiberTag = isHostHoistableType(type, pendingProps, hostContext)
? HostHoistable
: HostComponent;
} else if (supportsSingletons) {
fiberTag = isHostSingletonType(type) ? HostSingleton : HostComponent;
} else {
fiberTag = HostComponent;
}
} else {
getTag: switch (type) {
case REACT_FRAGMENT_TYPE:
return createFiberFromFragment(pendingProps.children, mode, lanes, key);
case REACT_STRICT_MODE_TYPE:
fiberTag = Mode;
mode |= StrictLegacyMode;
if (disableLegacyMode || (mode & ConcurrentMode) !== NoMode) {
// Strict effects should never run on legacy roots
mode |= StrictEffectsMode;
if (
enableDO_NOT_USE_disableStrictPassiveEffect &&
pendingProps.DO_NOT_USE_disableStrictPassiveEffect
) {
mode |= NoStrictPassiveEffectsMode;
}
}
break;
case REACT_PROFILER_TYPE:
return createFiberFromProfiler(pendingProps, mode, lanes, key);
case REACT_SUSPENSE_TYPE:
return createFiberFromSuspense(pendingProps, mode, lanes, key);
case REACT_SUSPENSE_LIST_TYPE:
return createFiberFromSuspenseList(pendingProps, mode, lanes, key);
case REACT_OFFSCREEN_TYPE:
return createFiberFromOffscreen(pendingProps, mode, lanes, key);
case REACT_LEGACY_HIDDEN_TYPE:
if (enableLegacyHidden) {
return createFiberFromLegacyHidden(pendingProps, mode, lanes, key);
}
// Fall through
case REACT_SCOPE_TYPE:
if (enableScopeAPI) {
return createFiberFromScope(type, pendingProps, mode, lanes, key);
}
// Fall through
case REACT_TRACING_MARKER_TYPE:
if (enableTransitionTracing) {
return createFiberFromTracingMarker(pendingProps, mode, lanes, key);
}
// Fall through
case REACT_DEBUG_TRACING_MODE_TYPE:
if (enableDebugTracing) {
fiberTag = Mode;
mode |= DebugTracingMode;
break;
}
// Fall through
default: {
if (typeof type === 'object' && type !== null) {
switch (type.$$typeof) {
case REACT_PROVIDER_TYPE:
if (!enableRenderableContext) {
fiberTag = ContextProvider;
break getTag;
}
// Fall through
case REACT_CONTEXT_TYPE:
if (enableRenderableContext) {
fiberTag = ContextProvider;
break getTag;
} else {
fiberTag = ContextConsumer;
break getTag;
}
case REACT_CONSUMER_TYPE:
if (enableRenderableContext) {
fiberTag = ContextConsumer;
break getTag;
}
// Fall through
case REACT_FORWARD_REF_TYPE:
fiberTag = ForwardRef;
if (__DEV__) {
resolvedType = resolveForwardRefForHotReloading(resolvedType);
}
break getTag;
case REACT_MEMO_TYPE:
fiberTag = MemoComponent;
break getTag;
case REACT_LAZY_TYPE:
fiberTag = LazyComponent;
resolvedType = null;
break getTag;
}
}
let info = '';
let typeString;
if (__DEV__) {
if (
type === undefined ||
(typeof type === 'object' &&
type !== null &&
Object.keys(type).length === 0)
) {
info +=
' You likely forgot to export your component from the file ' +
"it's defined in, or you might have mixed up default and named imports.";
}
if (type === null) {
typeString = 'null';
} else if (isArray(type)) {
typeString = 'array';
} else if (
type !== undefined &&
type.$$typeof === REACT_ELEMENT_TYPE
) {
typeString = `<${
getComponentNameFromType(type.type) || 'Unknown'
} />`;
info =
' Did you accidentally export a JSX literal instead of a component?';
} else {
typeString = typeof type;
}
const ownerName = owner ? getComponentNameFromOwner(owner) : null;
if (ownerName) {
info += '\n\nCheck the render method of `' + ownerName + '`.';
}
} else {
typeString = type === null ? 'null' : typeof type;
}
fiberTag = Throw;
pendingProps = new Error(
'Element type is invalid: expected a string (for built-in ' +
'components) or a class/function (for composite components) ' +
`but got: ${typeString}.${info}`,
);
resolvedType = null;
}
}
}
const fiber = createFiber(fiberTag, pendingProps, key, mode);
fiber.elementType = type;
fiber.type = resolvedType;
fiber.lanes = lanes;
return fiber;
}
这里前一大块的代码都是,通过传入的type值去处理fiberTag的值,最后通过createFiber函数创建Fiber节点并返回;
最终会返回到performUnitOfWork函数中,我们再来看一下其内容:
// 执行一个工作单元(Fiber节点)的函数
function performUnitOfWork(unitOfWork: Fiber): void {
// 获取当前已提交的Fiber状态,即alternate
const current = unitOfWork.alternate;
// 初始化下一个工作单元的引用
let next;
next = beginWork(current, unitOfWork, entangledRenderLanes);
// 将pendingProps复制到memoizedProps,表示当前工作单元的props已经处理
unitOfWork.memoizedProps = unitOfWork.pendingProps;
// 如果beginWork没有返回新的工作(即没有新的子节点或兄弟节点需要处理)
if (next === null) {
// 完成当前工作单元的工作
completeUnitOfWork(unitOfWork);
} else {
// 如果有新的工作,将其设置为下一个工作单元
workInProgress = next;
}
}
当当前节点即HostFiber节点被beginWork处理完成之后得到next节点将其赋值给workInProgress。
然后流程将回到workLoopSync中;
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
进入下一次循环,调用performUnitOfWork,此时等待被处理的节点变为了<App>
当一个节点的子节点被全部执行完之后,performUnitOfWork中next会为空,此时会进入completeUnitOfWork中,
completeUnitOfWork
// 处理完当前的工作单元(Fiber),然后移动到下一个兄弟节点。
// 如果没有更多的兄弟节点,返回到父Fiber。
function completeUnitOfWork(unitOfWork: Fiber): void {
// 尝试完成当前的工作单元,然后移动到下一个兄弟。
// 如果没有更多的兄弟,返回到父Fiber。
let completedWork: Fiber = unitOfWork;
do {
// 获取节点对应的current
const current = completedWork.alternate;
const returnFiber = completedWork.return;
let next;
// 完成当前Fiber的工作。
next = completeWork(current, completedWork, entangledRenderLanes);
// 如果过程中产生了其他的子节点, 则回到`beginWork`阶段进行处理
if (next !== null) {
workInProgress = next;
return;
}
// 获取当前Fiber的兄弟Fiber。
const siblingFiber = completedWork.sibling;
// 有兄弟节点, 返回之后再次进入`beginWork`
if (siblingFiber !== null) {
workInProgress = siblingFiber;
return;
}
completedWork = returnFiber;
// 移动指针, 指向下一个节点更新workInProgress
workInProgress = completedWork;
} while (completedWork !== null);
// 到达了根节点。
if (workInProgressRootExitStatus === RootInProgress) {
// 更新根节点的状态从RootInProgress变为RootCompleted。
workInProgressRootExitStatus = RootCompleted;
}
}
来看看completeWork是如何处理一个节点已完成的:
completeWork的主要作用是根据Fiber节点的类似生成DOM节点
// 完成当前工作进度(Fiber),并返回下一个工作进度(如果有)。
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
const newProps = workInProgress.pendingProps;
popTreeContext(workInProgress);
switch (workInProgress.tag) {
case IncompleteFunctionComponent:
if (disableLegacyMode) {
break;
}
// 继续执行
case LazyComponent:
case SimpleMemoComponent:
case FunctionComponent:
case ForwardRef:
case Fragment:
case Mode:
case Profiler:
case ContextConsumer:
// 省略后续的case语句
}
函数中有很长的case语句,主要是根据Fiber节点的tag去处理不同类型的节点 主要需要处理的有以下节点:
HostComponent原生DOM节点HostText文本节点HostRoot根节点
来看一下具体的处理过程:
HostComponent:
case HostComponent: {
// 弹出当前的宿主上下文。
popHostContext(workInProgress);
const type = workInProgress.type;
// 如果当前 Fiber 节点之前已经渲染过(即不是首次挂载),并且已经有了 stateNode,
// 更新元素
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent(
current,
workInProgress,
type,
newProps,
renderLanes,
);
} else {
// 如果是首次挂载(没有 current Fiber),或者没有新的属性(newProps),
// 则进行首次挂载的逻辑。
// 获取当前的宿主上下文。
const currentHostContext = getHostContext();
// 创建一个新的宿主实例。
const rootContainerInstance = getRootHostContainer();
const instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);
appendAllChildren(instance, workInProgress, false, false);
workInProgress.stateNode = instance;
// 对于某些渲染器,需要在提交阶段处理初始挂载的副作用(例如 DOM 渲染器支持自动聚焦)。
if (finalizeInitialChildren(
instance,
type,
newProps,
currentHostContext,
)) {
markUpdate(workInProgress);
}
}
// 处理需要冒泡的属性
bubbleProperties(workInProgress);
return null;
}
首次挂载的情况下会进入createInstance
// 创建一个新的宿主实例(DOM元素)。
export function createInstance(
type: string, // 元素类型(例如 'div', 'span' 等)
props: Props, // 元素属性
rootContainerInstance: Container, // 根容器实例
hostContext: HostContext, // 宿主上下文
internalInstanceHandle: Object, // 内部实例句柄
): Instance {
let hostContextProd: HostContextProd;
if (__DEV__) {
// 在开发模式下,进行额外的验证。
const hostContextDev: HostContextDev = (hostContext: any);
validateDOMNesting(type, hostContextDev.ancestorInfo);
hostContextProd = hostContextDev.context;
} else {
// 在生产模式下,直接使用宿主上下文。
hostContextProd = (hostContext: any);
}
// 获取根容器的 ownerDocument。
const ownerDocument = getOwnerDocumentFromRootContainer(
rootContainerInstance,
);
let domElement: Instance;
// 根据宿主上下文创建元素。
switch (hostContextProd) {
case HostContextNamespaceSvg:
// 如果宿主上下文是 SVG,创建一个 SVG 元素。
domElement = ownerDocument.createElementNS(SVG_NAMESPACE, type);
break;
case HostContextNamespaceMath:
// 如果宿主上下文是 MathML,创建一个 MathML 元素。
domElement = ownerDocument.createElementNS(MATH_NAMESPACE, type);
break;
default:
// 如果不是 SVG 或 MathML,根据类型创建元素。
switch (type) {
case 'svg':
// 如果类型是 'svg',创建一个 SVG 元素。
domElement = ownerDocument.createElementNS(SVG_NAMESPACE, type);
break;
case 'math':
// 如果类型是 'math',创建一个 MathML 元素。
domElement = ownerDocument.createElementNS(MATH_NAMESPACE, type);
break;
case 'script':
// 为了避免脚本执行,通过 innerHTML 创建 script 元素。
const div = ownerDocument.createElement('div');
if (__DEV__) {
if (enableTrustedTypesIntegration && !didWarnScriptTags) {
console.error(
'Encountered a script tag while rendering React component. ' +
'Scripts inside React components are never executed when rendering ' +
'on the client. Consider using template tag instead ' +
'(https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template).',
);
didWarnScriptTags = true;
}
}
div.innerHTML = '<script><' + '/script>';
// 从 div 中移除 script 元素。
const firstChild = ((div.firstChild: any): HTMLScriptElement);
domElement = div.removeChild(firstChild);
break;
case 'select':
// 处理 select 元素的特殊情况。
if (typeof props.is === 'string') {
domElement = ownerDocument.createElement('select', {is: props.is});
} else {
domElement = ownerDocument.createElement('select');
}
if (props.multiple) {
domElement.multiple = true;
} else if (props.size) {
domElement.size = props.size;
}
break;
default:
// 对于其他类型的元素,根据 props.is 创建元素。
if (typeof props.is === 'string') {
domElement = ownerDocument.createElement(type, {is: props.is});
} else {
domElement = ownerDocument.createElement(type);
}
if (__DEV__) {
if (type.indexOf('-') === -1) {
// 如果类型不是自定义元素(不包含 '-'),并且不是小写字母开头(不是 HTML 元素)。
if (type !== type.toLowerCase()) {
console.error(
'<%s /> is using incorrect casing. ' +
'Use PascalCase for React components, ' +
'or lowercase for HTML elements.',
type,
);
}
if (
// $FlowFixMe[method-unbinding]
Object.prototype.toString.call(domElement) === '[object HTMLUnknownElement]' &&
!hasOwnProperty.call(warnedUnknownTags, type)
) {
warnedUnknownTags[type] = true;
console.error(
'The tag <%s> is unrecognized in this browser. ' +
'If you meant to render a React component, start its name with ' +
'an uppercase letter.',
type,
);
}
}
}
}
}
// 缓存 Fiber 节点和 DOM 元素的引用。
precacheFiberNode(internalInstanceHandle, domElement);
// 更新 DOM 元素的属性。
updateFiberProps(domElement, props);
return domElement;
}
这里函数主要做的是根据元素的类型使用DOM环境下的api进行元素的创建,比如这里需要创建一个div节点的话, document.createElement('div') 会被执行从而创建了一个div元素
此时return 后HostComponent中会得到创建的元素instance:
const instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);
appendAllChildren(instance, workInProgress, false, false);
workInProgress.stateNode = instance;
此时appendAllChildren会以递归的方式将当前节点的子节点对应的DOM元素添加在当前DOM元素之中
// 将 Fiber 树的所有子节点追加到宿主容器中。
function appendAllChildren(
parent: Instance,
workInProgress: Fiber,
needsVisibilityToggle: boolean,
isHidden: boolean,
) {
let node = workInProgress.child;
// 只有有child的情况需要处理
while (node !== null) {
if (node.tag === HostComponent || node.tag === HostText) {
// 处理原生DOM节点
appendInitialChild(parent, node.stateNode);
} else if (node.child !== null) {
// 递归
node.child.return = node;
node = node.child;
continue;
}
if (node === workInProgress) {
return;
}
while (node.sibling === null) {
if (node.return === null || node.return === workInProgress) {
return;
}
node = node.return;
}
// 处理兄弟节点
node.sibling.return = node.return;
node = node.sibling;
}
}
这里appendInitialChild就是使用原生的appendChild方法将node.stateNode添加到parent上
回到上面HostComponent还调用了finalizeInitialChildren方法:
export function finalizeInitialChildren(
domElement: Instance,
type: string,
props: Props,
hostContext: HostContext,
): boolean {
setInitialProperties(domElement, type, props);
switch (type) {
case 'button':
case 'input':
case 'select':
case 'textarea':
return !!props.autoFocus;
case 'img':
return true;
default:
return false;
}
}
针对特殊的元素打上更新标记
核心在setInitialProperties函数中:
export function setInitialProperties(
domElement: Element,
tag: string,
props: Object,
): void {
if (__DEV__) {
validatePropertiesInDevelopment(tag, props);
}
// TODO: Make sure that we check isMounted before firing any of these events.
switch (tag) {
case 'div':
case 'span':
case 'svg':
case 'path':
case 'a':
case 'g':
case 'p':
case 'li': {
// Fast track the most common tag types
break;
}
// img tags previously were implemented as void elements with non delegated events however Safari (and possibly Firefox)
// begin fetching the image as soon as the `src` or `srcSet` property is set and if we set these before other properties
// that can modify the request (such as crossorigin) or the resource fetch (such as sizes) then the browser will load
// the wrong thing or load more than one thing. This implementation ensures src and srcSet are set on the instance last
case 'img': {
listenToNonDelegatedEvent('error', domElement);
listenToNonDelegatedEvent('load', domElement);
// Mostly a port of Void Element logic with special casing to ensure srcset and src are set last
let hasSrc = false;
let hasSrcSet = false;
for (const propKey in props) {
if (!props.hasOwnProperty(propKey)) {
continue;
}
const propValue = props[propKey];
if (propValue == null) {
continue;
}
switch (propKey) {
case 'src':
hasSrc = true;
break;
case 'srcSet':
hasSrcSet = true;
break;
case 'children':
case 'dangerouslySetInnerHTML': {
// TODO: Can we make this a DEV warning to avoid this deny list?
throw new Error(
`${tag} is a void element tag and must neither have \`children\` nor ` +
'use `dangerouslySetInnerHTML`.',
);
}
// defaultChecked and defaultValue are ignored by setProp
default: {
setProp(domElement, tag, propKey, propValue, props, null);
}
}
}
if (hasSrcSet) {
setProp(domElement, tag, 'srcSet', props.srcSet, props, null);
}
if (hasSrc) {
setProp(domElement, tag, 'src', props.src, props, null);
}
return;
}
case 'input': {
if (__DEV__) {
checkControlledValueProps('input', props);
}
// We listen to this event in case to ensure emulated bubble
// listeners still fire for the invalid event.
listenToNonDelegatedEvent('invalid', domElement);
let name = null;
let type = null;
let value = null;
let defaultValue = null;
let checked = null;
let defaultChecked = null;
for (const propKey in props) {
if (!props.hasOwnProperty(propKey)) {
continue;
}
const propValue = props[propKey];
if (propValue == null) {
continue;
}
switch (propKey) {
case 'name': {
name = propValue;
break;
}
case 'type': {
type = propValue;
break;
}
case 'checked': {
checked = propValue;
break;
}
case 'defaultChecked': {
defaultChecked = propValue;
break;
}
case 'value': {
value = propValue;
break;
}
case 'defaultValue': {
defaultValue = propValue;
break;
}
case 'children':
case 'dangerouslySetInnerHTML': {
if (propValue != null) {
throw new Error(
`${tag} is a void element tag and must neither have \`children\` nor ` +
'use `dangerouslySetInnerHTML`.',
);
}
break;
}
default: {
setProp(domElement, tag, propKey, propValue, props, null);
}
}
}
// TODO: Make sure we check if this is still unmounted or do any clean
// up necessary since we never stop tracking anymore.
validateInputProps(domElement, props);
initInput(
domElement,
value,
defaultValue,
checked,
defaultChecked,
type,
name,
false,
);
track((domElement: any));
return;
}
case 'select': {
if (__DEV__) {
checkControlledValueProps('select', props);
}
// We listen to this event in case to ensure emulated bubble
// listeners still fire for the invalid event.
listenToNonDelegatedEvent('invalid', domElement);
let value = null;
let defaultValue = null;
let multiple = null;
for (const propKey in props) {
if (!props.hasOwnProperty(propKey)) {
continue;
}
const propValue = props[propKey];
if (propValue == null) {
continue;
}
switch (propKey) {
case 'value': {
value = propValue;
// This is handled by initSelect below.
break;
}
case 'defaultValue': {
defaultValue = propValue;
// This is handled by initSelect below.
break;
}
case 'multiple': {
multiple = propValue;
// TODO: We don't actually have to fall through here because we set it
// in initSelect anyway. We can remove the special case in setProp.
}
// Fallthrough
default: {
setProp(domElement, tag, propKey, propValue, props, null);
}
}
}
validateSelectProps(domElement, props);
initSelect(domElement, value, defaultValue, multiple);
return;
}
case 'textarea': {
if (__DEV__) {
checkControlledValueProps('textarea', props);
}
// We listen to this event in case to ensure emulated bubble
// listeners still fire for the invalid event.
listenToNonDelegatedEvent('invalid', domElement);
let value = null;
let defaultValue = null;
let children = null;
for (const propKey in props) {
if (!props.hasOwnProperty(propKey)) {
continue;
}
const propValue = props[propKey];
if (propValue == null) {
continue;
}
switch (propKey) {
case 'value': {
value = propValue;
// This is handled by initTextarea below.
break;
}
case 'defaultValue': {
defaultValue = propValue;
break;
}
case 'children': {
children = propValue;
// Handled by initTextarea above.
break;
}
case 'dangerouslySetInnerHTML': {
if (propValue != null) {
// TODO: Do we really need a special error message for this. It's also pretty blunt.
throw new Error(
'`dangerouslySetInnerHTML` does not make sense on <textarea>.',
);
}
break;
}
default: {
setProp(domElement, tag, propKey, propValue, props, null);
}
}
}
// TODO: Make sure we check if this is still unmounted or do any clean
// up necessary since we never stop tracking anymore.
validateTextareaProps(domElement, props);
initTextarea(domElement, value, defaultValue, children);
track((domElement: any));
return;
}
case 'option': {
validateOptionProps(domElement, props);
for (const propKey in props) {
if (!props.hasOwnProperty(propKey)) {
continue;
}
const propValue = props[propKey];
if (propValue == null) {
continue;
}
switch (propKey) {
case 'selected': {
// TODO: Remove support for selected on option.
(domElement: any).selected =
propValue &&
typeof propValue !== 'function' &&
typeof propValue !== 'symbol';
break;
}
default: {
setProp(domElement, tag, propKey, propValue, props, null);
}
}
}
return;
}
case 'dialog': {
listenToNonDelegatedEvent('cancel', domElement);
listenToNonDelegatedEvent('close', domElement);
break;
}
case 'iframe':
case 'object': {
// We listen to this event in case to ensure emulated bubble
// listeners still fire for the load event.
listenToNonDelegatedEvent('load', domElement);
break;
}
case 'video':
case 'audio': {
// We listen to these events in case to ensure emulated bubble
// listeners still fire for all the media events.
for (let i = 0; i < mediaEventTypes.length; i++) {
listenToNonDelegatedEvent(mediaEventTypes[i], domElement);
}
break;
}
case 'image': {
// We listen to these events in case to ensure emulated bubble
// listeners still fire for error and load events.
listenToNonDelegatedEvent('error', domElement);
listenToNonDelegatedEvent('load', domElement);
break;
}
case 'details': {
// We listen to this event in case to ensure emulated bubble
// listeners still fire for the toggle event.
listenToNonDelegatedEvent('toggle', domElement);
break;
}
case 'embed':
case 'source':
case 'link': {
// These are void elements that also need delegated events.
listenToNonDelegatedEvent('error', domElement);
listenToNonDelegatedEvent('load', domElement);
// We fallthrough to the return of the void elements
}
case 'area':
case 'base':
case 'br':
case 'col':
case 'hr':
case 'keygen':
case 'meta':
case 'param':
case 'track':
case 'wbr':
case 'menuitem': {
// Void elements
for (const propKey in props) {
if (!props.hasOwnProperty(propKey)) {
continue;
}
const propValue = props[propKey];
if (propValue == null) {
continue;
}
switch (propKey) {
case 'children':
case 'dangerouslySetInnerHTML': {
// TODO: Can we make this a DEV warning to avoid this deny list?
throw new Error(
`${tag} is a void element tag and must neither have \`children\` nor ` +
'use `dangerouslySetInnerHTML`.',
);
}
// defaultChecked and defaultValue are ignored by setProp
default: {
setProp(domElement, tag, propKey, propValue, props, null);
}
}
}
return;
}
default: {
if (isCustomElement(tag, props)) {
for (const propKey in props) {
if (!props.hasOwnProperty(propKey)) {
continue;
}
const propValue = props[propKey];
if (propValue === undefined) {
continue;
}
setPropOnCustomElement(
domElement,
tag,
propKey,
propValue,
props,
undefined,
);
}
return;
}
}
}
for (const propKey in props) {
if (!props.hasOwnProperty(propKey)) {
continue;
}
const propValue = props[propKey];
if (propValue == null) {
continue;
}
setProp(domElement, tag, propKey, propValue, props, null);
}
}
函数的作用如下:
- 处理特殊元素的事件绑定
- 对元素的
props进行校验 - 根据
props属性的类型和值进行不同的处理, 处理为DOM元素的真实事件和属性
图解Fiber树初次构造
我们以如下图所示代码作为App组件看一下Fiber树的具体构造过程:
1.进入渲染循环构造Fiber前:
prepareFreshStack函数调用了createWorkInProgress方法根据旧的hostFiber节点创建新的hostFiber节点,并且将新的节点赋值给了workInProgress
2.进入渲染循环workLoopSync:
performUnitOfWork执行, beginWork执行,根据节点类型updateHostRoot被调用,进入到reconcileChildren之中经过一系列调用,最终createFiber执行创建子Fiber节点App
beginWork执行最后,返回新构造好的下级Fiber节点App,在performUnitOfWork中,将workInProgress指向App
此时workInProgress不为空,再次进入performUnitOfWork, beginWork执行, 节点类型为updateHostComponent(元素节点),进入到reconcileChildren,创建子Fiber节点<div>,后续过程同上一步
performUnitOfWork执行, beginWork执行,根据节点类型updateHostComponent被调用,
这里由于子元素是数组,进入reconcileChildren在其内部会循环处理并列的节点,处理结果如下:
由于<div>hello</div>内部是一个文本节点,不需要再处理成Fiber节点,当处理完这个节点之后,beginWork返回的next是一个null,流程会进入到completeUnitOfWork中, completeWork函数执行:
此时节点对应的DOM实例被创建,并且通过workInProgress.stateNode与Fiber树节点进行关联
completeWork执行结束, 返回到completeUnitOfWork中,workInProgress此时指向Fiber节点div的sibling节点<ul>, 对应的Fiber元素被创建,进入reconcileChildren在其内部会循环处理并列的li节点,workInProgress指向第一个li节点,由于li节点下没有其他节点了,会进入completeUnitOfWork流程,此时第一个li节点的DOM实例处理完成。以此类推直至所有li处理完成
,此时workInProgress指回节点ul:
节点ul的completeWork执行,在appendAllChildren中,子节点创建的对应DOM元素被append在ul上,workInProgress上移:
直至回到HostRootFiber,HostRootFiber父节点为空,清空workInProgress,退出渲染循环
Fiber树构造完成,最终得到的Fiber树结构如下:
后续的过程将由commitRoot处理,在后续文章中更新
总结
本文从注册调度任务到渲染循环过程中的beginWork和completeWork两个阶段进行分析,讨论了React Fiber树的首次构造过程。