引言
前端框架React、Vue以及Angular已经三分天下数年。Angular因为版本升级造成的困扰,在国内已经几乎淡出前端人员的视野。可以说在国内是React和Vue在平分天下。而用过React的人都觉得它要比Vue香,它的声明式UI、用JavaScript编写组件间逻辑的灵活性以及大型应用渲染的速度都胜于Vue。
那React应用在mount时到底做了什么呢?本文从源码角度来解析它的执行逻辑和架构。
Top Level API
我们开发中必然少不了如下代码
import React, { Component } from 'react';
class Comp extends Component {
onChange = () => {
this.setState(...)
}
render () {
return ...
}
}
React提供给我们的Top Level API 到目前的V17版本,一直都没变过。只是在V16.8时引入的HOOks的概念,但是并不影响我们开发。
这些Top Level API是如何实现的?
我们看一段React.Component的源码:
本文所有源码以V17.01版本为准。
// 本段源码在文件 packages/react/src/ReactBaseClasses.js 中
function Component(props, context, updater) {
this.props = props;
this.context = context;
// 省略注释
this.refs = emptyObject;
// 省略注释
this.updater = updater || ReactNoopUpdateQueue;
}
Component.prototype.isReactComponent = {};
// 省略注释
Component.prototype.setState = function(partialState, callback) {
// ...省略部分代码
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
// 省略注释
Component.prototype.forceUpdate = function(callback) {
this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};
可见,我们经常使用的this.setState修改状态其实是执行了,updater.enqueueSetState方法。updater默认是ReactNoopUpdateQueue对象。
// 本段源码在文件 packages/react/src/ReactNoopUpdateQueue.js 中
/**
* 这是一个抽象的API
*/
const ReactNoopUpdateQueue = {
// 省略注释
isMounted: function(publicInstance) {
return false;
},
// 省略注释
enqueueForceUpdate: function(publicInstance, callback, callerName) {
warnNoop(publicInstance, 'forceUpdate');
},
// 省略注释
enqueueReplaceState: function(
publicInstance,
completeState,
callback,
callerName,
) {
warnNoop(publicInstance, 'replaceState');
},
// 省略注释
enqueueSetState: function(
publicInstance,
partialState,
callback,
callerName,
) {
warnNoop(publicInstance, 'setState');
},
};
这下,我们清晰明了Top Level API在React中只是抽象层的API,并没有实现具体的功能。
那他具体的功能是在哪实现的呢?这个我们先卖个关子,并命名为 关子1⃣️。
其实Hooks也是这么设计的。我们平时使用的
import React, { useState } from 'react';
这些钩子函数在React中也都是抽象接口,并没有具体的功能。
感兴趣的可以去文件 packages/react/src/ReactHooks.js 中看看各个Hook的抽象实现。
render方法
我们都知道React渲染的入口是
ReactDOM.render(<App />, document.getElementById('root'));
我们就看看render方法都做了什么。
render方法主要做了以下几件事:
- 给DOM对象root挂载 _reactRootContainer 私有属性
- _reactRootContainer私有属性上挂载 _internalRoot 属性即FiberRootNode对象
- 创建HostRootFiber对象
- 赋值FiberRootNode对象的current指向HostRootFiber,赋值HostRootFiber的stateNode指向FiberRootNode
- 非批量更新模式下调用updateContainer方法生成完整的Fiber树
FiberRootNode和HostRootFiber(也叫RootFiber)的关系如图:
FiberRootNode
FiberRootNode也可以叫FiberRoot是整个应用的起点。
它也是一个JavaScript对象,承载了应用更新过程中的信息。
比如ContainerInfo是渲染容器的根结点即render的第二个参数。
current是当前应用的Fiber对象,即是RootFiber。
finishedWork是已经完成任务的Fiber对象,在commit阶段会处理它。
还有一些xxxxLines属性是和优先级有关。
HostRootFiber
HostRootFiber也叫RootFiber是Fiber对象,它是Fiber树的根节点。
Fiber是React的核心,它可以理解为虚拟DOM。
它主要有以下两层含义:
1、从静态的数据结构来说,每个Fiber节点对应一个React Element,保存组件的类型和对应的DOM节点信息
2、从动态的工作单元来说,每个Fiber节点保存更新状态下组件改变的状态以及DOM最终需要进行操作的行为比如被删除、被插入到页面、被更新属性等
我们看看Fiber对象的属性:
// 本段源码在文件 packages/react-reconciler/src/ReactFiber.old.js 中
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
/** --- 静态数据结构的属性 --- */
// 对应的组件类型 Function/Class/Host...
this.tag = tag;
// 即jsx的key属性
this.key = key;
// 除特殊情况外,大部分情况与type相同
this.elementType = null;
// 如果是函数组件它是函数本身,如果是类组件它是类对象,如果是HostComponent它的DOM节点
this.type = null;
// 对应的真实DOM节点
this.stateNode = null;
/** --- 用于连接其他fiber节点形成fiber树 --- */
// 父节点
this.return = null;
// 第一个子节点
this.child = null;
// 兄弟节点
this.sibling = null;
this.index = 0;
this.ref = null;
/** --- 作为动态工作单元的属性 --- */
// 更新的props
this.pendingProps = pendingProps;
// 老的props
this.memoizedProps = null;
// 更新组成的链表,比如同时调用多个setState时
this.updateQueue = null;
// 老的state以及计算后的形成的新的state
this.memoizedState = null;
this.dependencies = null;
this.mode = mode;
/** --- 本次更新造成的DOM操作 --- */
this.flags = NoFlags;
this.nextEffect = null;
this.firstEffect = null;
this.lastEffect = null;
this.subtreeFlags = NoFlags;
this.deletions = null;
// 调度优先级相关
this.lanes = NoLanes;
this.childLanes = NoLanes;
// 该fiber在另一次更新时对应的fiber
this.alternate = null;
// ...省略部分代码
}
从上面代码中我们可以知道,每个Fiber节点都对应一个React Element,多个Fiber节点如何构建Fiber树的呢?就是依赖下面三个属性:
// 父节点
this.return = null;
// 第一个子节点
this.child = null;
// 兄弟节点
this.sibling = null;
我们举个例子:
function App() {
reutrn (
<div>
<p>闹闹前端</p>
<ul>
<li>javascript</li>
<li>nodejs</li>
</ul>
</div>
)
}
对应的Fiber树结构:
好,现在对FiberRootNode、HostRootFiber以及Fiber树有了了解。
接下来就正式进入到Fiber树的创建过程即React的render阶段,即Fiber树的创建过程以及DOM树的构建过程。
render阶段
render阶段是Fiber树的创建过程以及DOM树的构建过程,不要和上面说的ReactDOM.render方法搞混了。
render阶段主要执行了两个方法即beginWork和completeUnitOfWork。
先简单说一下,进入beginWork之前的函数调用路线。
beginWork前
在ReactDOM.render方法中会 非批量更新模式下调用updateContainer方法生成完整的Fiber树
// 本段源码在文件 packages/react-reconciler/src/ReactFiberReconciler.old.js 中
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): Lane {
// ... 省略部分代码
enqueueUpdate(current, update);
scheduleUpdateOnFiber(current, lane, eventTime);
return lane;
}
updateContainer方法内调用scheduleUpdateOnFiber方法。
// 本段源码在文件 packages/react-reconciler/src/ReactFiberWorkLoop.old.js 中
export function scheduleUpdateOnFiber(
fiber: Fiber,
lane: Lane,
eventTime: number,
) {
// ... 省略部分代码
if (lane === SyncLane) {
if (
// Check if we're inside unbatchedUpdates
(executionContext & LegacyUnbatchedContext) !== NoContext &&
// Check if we're not already rendering
(executionContext & (RenderContext | CommitContext)) === NoContext
) {
// ... 省略部分代码
performSyncWorkOnRoot(root);
} else {
// ... 省略部分代码
}
} else {
// ... 省略部分代码
}
// ... 省略部分代码
}
scheduleUpdateOnFiber方法内调用performSyncWorkOnRoot方法。
// 本段源码在文件 packages/react-reconciler/src/ReactFiberWorkLoop.old.js 中
function performSyncWorkOnRoot(root) {
// ... 省略部分代码
let lanes;
let exitStatus;
if (
root === workInProgressRoot &&
includesSomeLane(root.expiredLanes, workInProgressRootRenderLanes)
) {
// ... 省略部分代码
} else {
lanes = getNextLanes(root, NoLanes);
exitStatus = renderRootSync(root, lanes);
}
// ... 省略部分代码
// 此段代码进入commit阶段
const finishedWork: Fiber = (root.current.alternate: any);
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
commitRoot(root);
// Before exiting, make sure there's a callback scheduled for the next
// pending level.
ensureRootIsScheduled(root, now());
return null;
}
performSyncWorkOnRoot方法内部执行renderRootSync方法。
// 本段源码在文件 packages/react-reconciler/src/ReactFiberWorkLoop.old.js 中
function renderRootSync(root: FiberRoot, lanes: Lanes) {
// ... 省略部分代码
do {
try {
workLoopSync();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
// ... 省略部分代码
return workInProgressRootExitStatus;
}
renderRootSync方法内部调用workLoopSync方法。
// 本段源码在文件 packages/react-reconciler/src/ReactFiberWorkLoop.old.js 中
function workLoopSync() {
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
这是个循环,workInProgress即为当前执行的Fiber。
// 本段源码在文件 packages/react-reconciler/src/ReactFiberWorkLoop.old.js 中
function performUnitOfWork(unitOfWork: Fiber): void {
const current = unitOfWork.alternate;
setCurrentDebugFiberInDEV(unitOfWork);
let next;
if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
startProfilerTimer(unitOfWork);
next = beginWork(current, unitOfWork, subtreeRenderLanes);
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
} else {
next = beginWork(current, unitOfWork, subtreeRenderLanes);
}
resetCurrentDebugFiberInDEV();
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// If this doesn't spawn new work, complete the current work.
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
ReactCurrentOwner.current = null;
}
这里先调用beginWork,后调用completeUnitOfWork。
我们先关注一下beginWork的参数
- current是unitOfWork.alternate即为当前fiber节点上一次更新后的fiber节点可以理解为老fiber节点
- unitOfWork既是workInProgress,它是当前组件对应的fiber节点
- SubtreeRenderLanes和优先级有关。本篇不涉及到优先级,所以这里忽略。
beginWork
beiginWork会根据传入的current是否存在,区别是mount还是update渲染。
在mount时,除了FiberRootNode外,其他的fiber节点的alternate肯定是null。
它会根据fiber.tag的不同,创建不同的子fiber节点。
// 本段源码在文件 packages/react-reconciler/src/ReactFiberBeginWork.old.js 中
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
const updateLanes = workInProgress.lanes;
// ...省略部分代码
// current存在即为update更新渲染,可以做一些优化比如复用current节点
if (current !== null) {
// ...省略部分代码
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
} else {
// ...省略部分代码
} else {
didReceiveUpdate = false;
}
// ...省略部分代码
// current === null即mount时
switch (workInProgress.tag) {
// ...省略部分tag类型的处理代码
// 函数组件
case FunctionComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
// 类组件
case ClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
}
// ...省略部分tag类型的处理代码
}
}
// ...省略部分代码
}
可以看出,根据workInProgress.tag类型调用相应的updateXXX方法进行相应组件类型的处理。具体的tag类型可以参考文件 packages/react-reconciler/src/ReactWorkTags.js
我们以ClassComponent为例。
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderLanes,
);
先解析defaultProps,然后调用updateClassComponent方法。
updateClassComponent
// 本段源码在文件 packages/react-reconciler/src/ReactFiberBeginWork.old.js 中
function updateClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps: any,
renderLanes: Lanes,
) {
// ...省略部分代码
const instance = workInProgress.stateNode;
let shouldUpdate;
if (instance === null) {
if (current !== null) {
// ...省略部分代码
}
// mount时,执行
constructClassInstance(workInProgress, Component, nextProps);
mountClassInstance(workInProgress, Component, nextProps, renderLanes);
shouldUpdate = true;
} else if (current === null) {
// ...省略部分代码
} else {
// ...省略部分代码
}
const nextUnitOfWork = finishClassComponent(
current,
workInProgress,
Component,
shouldUpdate,
hasContext,
renderLanes,
);
// ...省略部分代码
return nextUnitOfWork;
}
mount时执行constructClassInstance即使用new运算符实例化此类组件同时设定累的updater对象,mountClassInstance调用前期的生命周期方法比如componentWillMount以及静态方法getDerivedStateFromProps。
接着调用finishClassComponent,它调用类对象上的render方法,进入Reconciler阶段,根据React Element创建对应的Fiber节点,并返回这个节点作为下一个执行的单元任务。
// 本段源码在文件 packages/react-reconciler/src/ReactFiberClassComponent.old.js 中
function constructClassInstance(
workInProgress: Fiber,
ctor: any,
props: any,
): any {
// ...省略部分代码
// 实例化类组件
const instance = new ctor(props, context);
const state = (workInProgress.memoizedState =
instance.state !== null && instance.state !== undefined
? instance.state
: null);
adoptClassInstance(workInProgress, instance);
// ...省略部分代码
return instance;
}
这里的关键是adoptClassInstance方法。
// 本段源码在文件 packages/react-reconciler/src/ReactFiberClassComponent.old.js 中
function adoptClassInstance(workInProgress: Fiber, instance: any): void {
instance.updater = classComponentUpdater;
workInProgress.stateNode = instance;
// ...省略部分代码
}
这里给实例对象挂载updater属性,对应的值是classComponentUpdater,这个对象就是我们在开篇说Component时setState方法的具体实现了。也就是关子1⃣️的答案。
// 本段源码在文件 packages/react-reconciler/src/ReactFiberClassComponent.old.js 中
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) {
if (__DEV__) {
warnOnInvalidCallback(callback, 'setState');
}
update.callback = callback;
}
// 构建更新的链表
enqueueUpdate(fiber, update);
// 进入更新逻辑,回归beginWork
scheduleUpdateOnFiber(fiber, lane, eventTime);
// ...省略部分代码
},
enqueueReplaceState(inst, payload, callback) {
// ...省略代码
},
enqueueForceUpdate(inst, callback) {
// ...省略代码
},
};
当我们在类组件里执行this.setState时,便会执行enqueueSetState方法。
enqueueSetState先创建update对象,然后构建updateQueue链表,最后执行scheduleUpdateOnFiber方法,回归到beginWork的更新状态。
我们接着看finishClassComponent方法吧。
// 本段源码在文件 packages/react-reconciler/src/ReactFiberBeginWork.old.js 中
function finishClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
shouldUpdate: boolean,
hasContext: boolean,
renderLanes: Lanes,
) {
// ...省略部分代码
const instance = workInProgress.stateNode;
ReactCurrentOwner.current = workInProgress;
let nextChildren;
if (
didCaptureError &&
typeof Component.getDerivedStateFromError !== 'function'
) {
// ...省略部分代码
} else {
if (__DEV__) {
// ...省略部分代码
} else {
// 调用实例对象的render方法,获取React Element对象
nextChildren = instance.render();
}
}
workInProgress.flags |= PerformedWork;
if (current !== null && didCaptureError) {
// ...省略部分代码
} else {
// 进入调和阶段,这是Reconciler的核心模块
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
}
workInProgress.memoizedState = instance.state;
// ...省略部分代码
return workInProgress.child;
}
重点关注instance.render方法即我们书写在类组件里的render方法,它返回的是jsx。我们都知道React会把jsx转化为React Element对象。nextChildren就是转化后的React Element对象,reconcileChildren就是对这些对象进行调和的。
经过reconcileChildren后,便会赋值workInProgress的child对象。
reconcileChildren
函数reconcileChildren在文件 packages/react-reconciler/src/ReactFiberBeginWork.old.js 中,它同样根据current是否为null区分是mount还是update。如果是mount调用mountChildFibers方法,否则调用reconcileChildFibers方法。这里我们只看mountChildFibers方法。
// 本段源码在文件 packages/react-reconciler/src/ReactChildFiber.old.js 中
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
lanes: Lanes,
): Fiber | null {
// ...省略部分代码
// Handle object types
const isObject = typeof newChild === 'object' && newChild !== null;
if (isObject) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
return placeSingleChild(
reconcileSingleElement(
returnFiber,
currentFirstChild,
newChild,
lanes,
),
);
case REACT_PORTAL_TYPE:
return placeSingleChild(
reconcileSinglePortal(
returnFiber,
currentFirstChild,
newChild,
lanes,
),
);
case REACT_LAZY_TYPE:
if (enableLazyElements) {
const payload = newChild._payload;
const init = newChild._init;
// TODO: This function is supposed to be non-recursive.
return reconcileChildFibers(
returnFiber,
currentFirstChild,
init(payload),
lanes,
);
}
}
}
if (typeof newChild === 'string' || typeof newChild === 'number') {
return placeSingleChild(
reconcileSingleTextNode(
returnFiber,
currentFirstChild,
'' + newChild,
lanes,
),
);
}
if (isArray(newChild)) {
return reconcileChildrenArray(
returnFiber,
currentFirstChild,
newChild,
lanes,
);
}
// ...省略部分代码
return deleteRemainingChildren(returnFiber, currentFirstChild);
}
return reconcileChildFibers;
}
可以看到它根据不同类型,调用不同方法调和节点。我们先看调和单个节点的实现即reconcileSingleElement方法
// 本段源码在文件 packages/react-reconciler/src/ReactChildFiber.old.js 中
function reconcileSingleElement(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
element: ReactElement,
lanes: Lanes,
): Fiber {
const key = element.key;
let child = currentFirstChild;
while (child !== null) {
// 这里是更新时执行
// ...省略部分代码
}
if (element.type === REACT_FRAGMENT_TYPE) {
const created = createFiberFromFragment(
element.props.children,
returnFiber.mode,
lanes,
element.key,
);
created.return = returnFiber;
return created;
} else {
const created = createFiberFromElement(element, returnFiber.mode, lanes);
created.ref = coerceRef(returnFiber, currentFirstChild, element);
created.return = returnFiber;
return created;
}
}
在mount时到这,就根据element.type类型创建当前的fiber节点即createFiberFromElement方法和createFiberFromFragment。并赋值新节点的return指针。
我们再看看多节点的调和即reconcileChildrenArray方法
// 本段源码在文件 packages/react-reconciler/src/ReactChildFiber.old.js 中
function reconcileChildrenArray(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChildren: Array<*>,
lanes: Lanes,
): Fiber | null {
// ...省略部分代码
let resultingFirstChild: Fiber | null = null;
let previousNewFiber: Fiber | null = null;
// mount时oldFiber为null
let oldFiber = currentFirstChild;
let lastPlacedIndex = 0;
let newIdx = 0;
let nextOldFiber = null;
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
// ...更新是逻辑
}
if (newIdx === newChildren.length) {
deleteRemainingChildren(returnFiber, oldFiber);
return resultingFirstChild;
}
if (oldFiber === null) {
for (; newIdx < newChildren.length; newIdx++) {
// 创建子节点
const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
if (newFiber === null) {
continue;
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
// 保存第一个子节点
resultingFirstChild = newFiber;
} else {
// 把上一个子节点的sibling指向当前新创建的子节点
previousNewFiber.sibling = newFiber;
}
// 保存当前子节点为上一个子节点
previousNewFiber = newFiber;
}
// 返回第一个子节点
return resultingFirstChild;
}
// ...省略更新是逻辑
return resultingFirstChild;
}
在mount时,从数组第一项开始遍历,创建第一个子节点时保存在resultingFirstChild上。
接着如果previousNewFiber为null也就是创建第一个子节点,把第一个子节点赋值给它,之后创建第二子节点时就可以执行previousNewFiber.sibling=newFiber即把第二子节点赋值给它的sibling指针,然后在把它指向第二子节点,这样第一个子节点的sibling就指向了第二个子节点。逐渐遍历下去,就有第二个子节点的sibling指向第三个子节点,第三个子节点的sibling指向第四个子节点。最后返回resultingFirstChild即第一个子节点。
返回的第一个子节点会在reconcileChildren方法中赋值给workInProgress.child。
这就是构建fiber树的具体过程。可以回头看看上面的Fiber树的图。
当然这里省略了更新时的调和也就是我们说的DOM Diff。因为本篇重点在mount的过程,所以这里不做解析。
在performUnitOfWork方法中,如果beinWork返回的结果为null就进入completeUnitOfWork方法处理当前的fiber节点。
而beginWork返回的是Fiber树的第一个子节点,也就是当前的深度遍历到底了。
completeUnitOfWork
在beinWork逻辑里我们只看到了创建Fiber树的过程,并没有看到如何创建真实DOM。
而completeUnitOfWork就是创建真实DOM的过程,并会遍历fiber树创建DOM树。
// 本段源码在文件 packages/react-reconciler/src/ReactFiberWorkLoop.old.js 中
function completeUnitOfWork(unitOfWork: Fiber): void {
let completedWork = unitOfWork;
do {
// ...省略部分代码
if ((completedWork.flags & Incomplete) === NoFlags) {
// ...省略部分代码
if (
!enableProfilerTimer ||
(completedWork.mode & ProfileMode) === NoMode
) {
next = completeWork(current, completedWork, subtreeRenderLanes);
} else {
// ...省略部分代码
next = completeWork(current, completedWork, subtreeRenderLanes);
// ...省略部分代码
}
resetCurrentDebugFiberInDEV();
if (next !== null) {
workInProgress = next;
return;
}
// ...省略部分代码
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
workInProgress = siblingFiber;
return;
}
completedWork = returnFiber;
workInProgress = completedWork;
} while (completedWork !== null);
// ...省略部分代码
}
此方法会调用completeWork方法,它会根据fiber.tag调用不同的处理逻辑,然后返回结果。
// 本段源码在文件 packages/react-reconciler/src/ReactFiberCompleteWork.old.js 中
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
// ...省略部分类型的处理逻辑
case HostComponent: {
popHostContext(workInProgress);
const rootContainerInstance = getRootHostContainer();
const type = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
// ...省略部分代码
} else {
// ...省略部分代码
if (wasHydrated) {
// ...省略部分代码
} else {
// 创建真实DOM
const instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);
// ...省略部分代码
// 添加子DOM到父DOM中
appendAllChildren(instance, workInProgress, false, false);
workInProgress.stateNode = instance;
if (
// 处理DOM的属性
finalizeInitialChildren(
instance,
type,
newProps,
rootContainerInstance,
currentHostContext,
)
) {
markUpdate(workInProgress);
}
}
// ...省略部分代码
}
// ...省略部分代码
return null;
}
case HostText: {
// ...省略部分代码
return null;
}
// ...省略类型处理逻辑
}
// ...省略部分代码
}
如果completeWork方法返回null就会判断有没有sibling,如果有则返回,再次进入beginWork逻辑;如果没有就会把它的return属性赋值给当前的workInProgress就进入到父节点的completeWork过程,从而创建父节点对应的真实DOM并添加子DOM。方法中的createInstance就是创建真实DOM,appendAllChildren方法是添加子DOM的过程。
方法createInstance在文件packages/react-dom/src/client/ReactDOMHostConfig.js中。
方法appendAllChildren在文件packages/react-reconciler/src/ReactFiberCompleteWork.old.js中。
大家可以自己去看看。
我们以下面为例,一步步解析
function App() {
reutrn (
<div>
<p>闹闹前端</p>
<ul>
<li>javascript</li>
<li>nodejs</li>
</ul>
</div>
)
}
第一步进入div的beginWork,它的子节点是Array有p和ul,所以会构建div对应的fiber节点(便于说明我们叫它Fiber_Div )的child是p(Fiber_p),Fiber_p的sibling是ul(Fiber_ul)然后返回Fiber_Div的child作为下一个工作单元。
第二步即为Fiber_p进入beginWork,会调用updateHostComponent方法,调和它的子节点即文本节点闹闹前端,返回null。
第三步进入completeUnitOfWork方法,unitOfWork即为Fiber_p。然后调用completeWork方法,创建真实p DOM,并返回null。经过一些处理后,获取它的sibling,如果它有sibling就直接返回,在performUnitOfWork中重新赋值给next,再返回next。再进入beginWork阶段,这时就是调和Fiber_ul了。
第四步对Fiber_ul进行第一步到第三步的深度遍历。包括它的子节点li。
第五步当进入第二个li的completeUninOfWork后,它的sibling是null。这时就会执行
completedWork = returnFiber;
workInProgress = completedWork;
它的returnFiber就是Fiber_ul,此时completedWork不为null,即进入while的第二次循环。
第六步进行Fiber_ul的completeWork,执行创建ul的真实DOM,并执行appendAllChildren方法,遍历它的child指针添加到真实的DOM中,从而构建真实的ul和li的DOM关系树。
第七步对Fiber_div执行同样的逻辑,创建真实的div DOM和以及它和p、ul的DOM关系树。
至此渲染阶段及render阶段结束。下面进入commit阶段。
commit阶段
方法commitRoot是commit阶段的入口。它执行commitRootImpl方法。
方法commitRootImpl的工作主要分为三部分:
-
执行commitBeforeMutationEffects方法即执行DOM操作前
-
执行commitMutationEffects方法即执行DOM操作,把DOM渲染到页面
-
执行commitLayoutEffects方法即执行DOM操作后
// 本段源码在文件 packages/react-reconciler/src/ReactFiberWorkLoop.old.js 中
function commitRootImpl(root, renderPriorityLevel) {
// ...省略部分代码
const finishedWork = root.finishedWork;
const lanes = root.finishedLanes;
// ...省略部分代码
root.finishedWork = null;
root.finishedLanes = NoLanes;
// ...省略部分代码
if (firstEffect !== null) {
// ...省略部分代码
nextEffect = firstEffect;
do {
if (__DEV__) {
// ...省略部分代码
} else {
try {
commitBeforeMutationEffects();
} catch (error) {
// ...省略部分代码
}
}
} while (nextEffect !== null);
// ...省略部分代码
nextEffect = firstEffect;
do {
if (__DEV__) {
// ...省略部分代码
} else {
try {
commitMutationEffects(root, renderPriorityLevel);
} catch (error) {
// ...省略部分代码
}
}
} while (nextEffect !== null);
// ...省略部分代码
nextEffect = firstEffect;
do {
if (__DEV__) {
// ...省略部分代码
} else {
try {
commitLayoutEffects(root, lanes);
} catch (error) {
// ...省略部分代码
}
}
} while (nextEffect !== null);
nextEffect = null;
// ...省略部分代码
} else {
// ...省略部分代码
}
// ...省略部分代码
return null;
}
commitBeforeMutationEffects
// 本段源码在文件 packages/react-reconciler/src/ReactFiberWorkLoop.old.js 中
function commitBeforeMutationEffects() {
while (nextEffect !== null) {
const current = nextEffect.alternate;
if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {
// ... focus blur逻辑
}
const flags = nextEffect.flags;
if ((flags & Snapshot) !== NoFlags) {
setCurrentDebugFiberInDEV(nextEffect);
// 类组件调用getSnapshotBeforeUpdate生命周期方法,HostRoot类型清空容器
commitBeforeMutationEffectOnFiber(current, nextEffect);
resetCurrentDebugFiberInDEV();
}
// 调度useEffect
if ((flags & Passive) !== NoFlags) {
// If there are passive effects, schedule a callback to flush at
// the earliest opportunity.
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
scheduleCallback(NormalSchedulerPriority, () => {
flushPassiveEffects();
return null;
});
}
}
nextEffect = nextEffect.nextEffect;
}
}
整体功能分为三部分
- 处理DOM的focus和blur逻辑
- 调用类组件的getSnapshotBeforeUpdate生命周期方法
- 调度useEffect
commitMutationEffects
// 本段源码在文件 packages/react-reconciler/src/ReactFiberWorkLoop.old.js 中
function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {
// 遍历effect链表
while (nextEffect !== null) {
setCurrentDebugFiberInDEV(nextEffect);
const flags = nextEffect.flags;
if (flags & ContentReset) {
commitResetTextContent(nextEffect);
}
// 更新ref
if (flags & Ref) {
const current = nextEffect.alternate;
if (current !== null) {
commitDetachRef(current);
}
if (enableScopeAPI) {
if (nextEffect.tag === ScopeComponent) {
commitAttachRef(nextEffect);
}
}
}
// 根据effectTag不同,执行不同的逻辑处理
const primaryFlags = flags & (Placement | Update | Deletion | Hydrating);
outer: switch (primaryFlags) {
// 插入DOM
case Placement: {
commitPlacement(nextEffect);
nextEffect.flags &= ~Placement;
break;
}
// 插入DOM并更新DOM
case PlacementAndUpdate: {
// Placement
commitPlacement(nextEffect);
nextEffect.flags &= ~Placement;
// Update
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
// ...省略SSR逻辑
// 更新DOM
case Update: {
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
// 删除DOM
case Deletion: {
const deletedChild = nextEffect;
const deletions = deletedChild.deletions;
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const deletion = deletions[i];
deletion.flags &= ~Deletion;
deletion.deletions = null;
commitDeletion(root, deletion, renderPriorityLevel);
}
}
break;
}
}
resetCurrentDebugFiberInDEV();
nextEffect = nextEffect.nextEffect;
}
}
此过程会根据effectTag,对每个Fiber节点进行增删改的操作。
方法commitPlacement 是插入DOM
// 本段源码在文件 packages/react-reconciler/src/ReactFiberCommitWork.old.js 中
function commitPlacement(finishedWork: Fiber): void {
if (!supportsMutation) {
return;
}
const parentFiber = getHostParentFiber(finishedWork);
let parent;
let isContainer;
// 获取父级DOM节点
const parentStateNode = parentFiber.stateNode;
switch (parentFiber.tag) {
case HostComponent:
parent = parentStateNode;
isContainer = false;
break;
case HostRoot:
parent = parentStateNode.containerInfo;
isContainer = true;
break;
case HostPortal:
parent = parentStateNode.containerInfo;
isContainer = true;
break;
case FundamentalComponent:
if (enableFundamentalAPI) {
parent = parentStateNode.instance;
isContainer = false;
}
default:
// ...省略部分代码
}
if (parentFiber.flags & ContentReset) {
resetTextContent(parent);
parentFiber.flags &= ~ContentReset;
}
// 获取兄弟DOM节点
const before = getHostSibling(finishedWork);
// 根据情况决定是调用insertBefore还是appendChild方法执行DOM操作
if (isContainer) {
insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
} else {
insertOrAppendPlacementNode(finishedWork, before, parent);
}
}
主要功能:
- 获取父级DOM节点
- 获取兄弟节点
- 根据情况决定是调用insertBefore还是appendChild方法执行DOM操作
在mount时,也是这在一步把Fiber上对应的DOM树渲染到root容器中的。
其他的更新commitWork和删除commitDeletion操作都是update时会执行的了,在这里就暂不讨论。
commitLayoutEffects
// 本段源码在文件 packages/react-reconciler/src/ReactFiberWorkLoop.old.js 中
function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) {
// ...省略部分代码
while (nextEffect !== null) {
setCurrentDebugFiberInDEV(nextEffect);
const flags = nextEffect.flags;
if (flags & (Update | Callback)) {
const current = nextEffect.alternate;
// 调用生命周期componentDidMount和componentDidUpdate和hook
commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes);
}
if (enableScopeAPI) {
if (flags & Ref && nextEffect.tag !== ScopeComponent) {
// 赋值ref
commitAttachRef(nextEffect);
}
} else {
if (flags & Ref) {
// 赋值ref
commitAttachRef(nextEffect);
}
}
// ...省略部分代码
nextEffect = nextEffect.nextEffect;
}
// ...省略部分代码
}
此方法主要功能如下:
- 调用生命周期方法和hook有关的操作
- 赋值ref
总结
本篇从React的Top Level API的实现,再到ReactDOM.render方法的执行流程,接着到渲染阶段创建Fiber树及构建DOM树的过程,最后到commit阶段把真实DOM渲染到页面的过程,讲述了应用第一次加载的流程。
下面用一张图总结一下整个流程:
图太模糊,拆分为一下三张图:
这样我们就系统的了解了React内部的一些架构设计和实现逻辑。过程还是比较复杂的,但是这也只是React的冰山一角。剩余的分支还有很多,比如React是如何更新状态及把状态映射到DOM上的?DOMDiff的具体实现及算法是什么?Hooks的实现及执行时机是什么?还有最重要的任务调度和异步渲染是如何实现的?都值得详细去探讨。
更多内容可以关注微信公众号:闹闹前端
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!