观察[[react]]应用初次调和的过程,整理关键路径,关键函数。
<!DOCTYPE html>
<html lang="en">
<head>
<title>React App</title>
</head>
<body>
<div id="container"></div>
</body>
</html>
import ReactDom from 'react-dom/client'
function FnComp(props) {
return <p>{props.children}</p>
}
class ClazzComp extends React.Component {
render() {
return <p style={{ color: 'red' }}>{this.props.children}</p>
}
}
ReactDOM.createRoot(document.getElementById('container')).render(
<div id="root">
<FnComp>hello</FnComp>
<ClazzComp>react</ClazzComp>
</div>
);
从性能面板上看,
performConcurrentWorkOnRoot完成渲染工作,内部又调用renderRootSync和finishConcurrentRender,所以react分2个阶段完成一次渲染:render阶段和commit阶段。
初始化工作
初始化工作包括新建fiberRoot对象,准备好workInProgress对象,为render阶段做准备。
// 首先运行`ReactDom.createRoot`,然后运行返回对象上的`render`方法。
// container就是渲染的dom节点,例子中是div#container
// 目的如函数名:创建fiberRoot对象和hostRootFiber对象
function createRoot(container) {
// createContainer最终调用到createFiberRoot
const root = createContainer(container);
// 返回ReactDomRoot实例
return new ReactDOMRoot(root);
}
function createFiberRoot(containerInfo) {
// 新建fiberRoot实例
const root: FiberRoot = new FiberRootNode(containerInfo);
// 新建一个fiber,类型是HostRoot,表示宿主环境
// fiber是react新调和方法的工作单元,不同的fiber类型有不同的调和方法
// 例子中类组件、函数组件、浏览器标签都有对应的fiber类型
const uninitializedFiber = createHostRootFiber();
// firberRoot和hostRootFiber互相关联
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;
return root;
}
// ReactDOMRoot的原型链有render方法,所以才能调用render方法
function ReactDOMRoot(internalRoot: FiberRoot) {
this._internalRoot = internalRoot;
}
ReactDOMRoot.prototype.render = function(children: ReactNodeList) {
const root = this._internalRoot;
// updateContainer会请求scheduler模块回调performConcurrentWorkOnFiber
// scheduler模块涉及到何时回调performConcurrentWorkOnFiber,本文暂不涉及
// 本文主要讲解performConcurrentWorkOnFiber函数在初始化渲染过程中关键实现逻辑
updateContainer(children, root);
}
目前可以得出fiberRootNode和uninitializedFiber对象(即:hostRootFiber对象)之间的引用关系:
fiberRootNode -- containerInfo --> div#container
| ^
| |
current stateNode
| |
V |
hostRootFiber
// 上面讲到performConcurrentWorkOnRoot,初次渲染运行renderRootSync函数
// renderRootSync运行workLoopSync函数
function workLoopSync(root: FiberRoot) {
// workInProgressRoot是一个全局变量,标记当前构建哪个fiberRoot
// 初始化null,显然不等于root,那么就是准备一颗新的fiber树
// react借鉴了git中的说法,将构建中的树成为workInProgress树
// 等到workInProgress树构建完成,再一次性修改到页面上,这个过程也叫commit
// 这也是为什么react的渲染过程分别叫做:render阶段和commit阶段
// render阶段就是构建workInProgress树
// commit阶段就是提交workInProgress树的所有修改
if (workInProgressRoot !== root) {
// 准备workInProgreess树
prepareFreshStack(root);
}
// workInProgress是一个全局变量,表示当前的fiber节点。
while(workInProgress !== null) {
// 只要有workInProgress,则不停调用performUnitOfWork
performUnitOfWork(workInProgress);
}
}
function prepareFreshStack(root) {
workInProgressRoot = root;
//
const rootWorkInProgress = createWorkInProgress(root.current);
// 这里将workInProgress设置成HostRootFiber,后续在workLoopSync中才能使用
workInProgress = rootWorkInProgress;
}
function createWorkInProgress(current: Fiber) {
// 创建一个HostRootFiber对象
// 所以准备workInProgress树其实就是新建hostRootFiber对象
const workInProgress = createFiber(current.tag, current.key);
// 使用alternate互相引用
workInProgress.alternate = current;
current.alternate = workInProgress;
// ...省略一些属性
return workInProgress;
}
目前可以得出fiberRoot、current、workInProgress的引用关系:
FiberRootNode
| ^
| |
workInProgress current stateNode
| | |
V V |
hostRootFiber <-- alternate --> hostRootFiber
render阶段
render阶段主要是在内容中构建出完整的workInProgress树,为commit阶段做准备
beginWork performUnitOfWork
function performUnitOfWork(unitOfWork: Fiber) {
const current = unitOfWork.alternate;
let next = beginWork(current, unitOfWork);
if (next !== null) {
// 目前的重点在这:只要next不为空,就一直执行performUnitOfWork
workInProgress = next;
} else {
// 暂时忽略
}
}
// beginWork是一个巨大的switch...case判断
// 不同的fiber类型,执行对应的updateXXXComponent的函数。
function beginWork(current: Fiber, workInProgress: Fiber) {
switch(workInProgress.tag) {
case ClassComponent: {
// 类组件
const Component = workInProgress.type;
const nextProps = workInProgress.pendingProps;
return updateClassComponent(
current, workInProgress,
Component, nextProps
)
}
case FunctionComponent:
// 函数组件
const Component = workInProgress.type;
const nextProps = workInProgress.pendingProps;
return updateFunctionComponent(
current, workInProgress,
Component, nextProps
)
case HostComponent:
// 浏览器内置标签
return updateHostComponent(/**/)
// ...
}
}
// 针对类组件:实例化组件,通过render函数得到子虚拟dom,调和,返回workInProgress.child
function updateClassComponent(
current: Fiber,
workInProgress: Fiber,
Component: any,
nextProps: any
) {
// 实例化
constructClassInstance(workInProgress, Component, nextProps);
mountClassInstance(workInProgress, Component, nextProps);
// 取finishClassComponent的返回值作为下一个工作单元
const nextUnitOfWork = finishClassComponent(current, workInProgress, Component)
return nextUnitOfWork;
}
function constructClassInstance(workInProgress, ctor, props) {
const instance = new ctor(props);
workInProgress.stateNode = instance;
}
function mountClassInstance(workInProgress, ctor, newProps) {}
function finishClassComponent(current, workInProgress, Component) {
const instance = workInProgress.stateNode;
// 运行render方法,得到子虚拟dom,例子中就是h1对应的虚你dom
let nextChildren = instance.render();
// 调和
reconcileChildren(current, workInProgress, nextChildren);
// 返回workInProgress.child属性
return workInProgress.child;
}
// 针对函数组件:运行函数得到子虚拟dom,调和,返回workInProgress.child
function updateFunctionComponent(current, workInProgress, Component, nextProps) {
// 用新的props生成新的虚拟dom
let nextChildren = renderWithHooks(current, workInProgress, Component, nextProps);
// 调和
reconcileChildren(current, workInProgress, nextChildren);
// 返回child
return workInProgress.child;
}
function renderWithHooks(current, workInProgress, Component, props) {
// 运行函数组件函数,得到虚拟dom
// 暂时可以忽略其他代码,作用是提高hooks相关api,以后再讲
let children = Component(props);
return children;
}
// 针对浏览器标签:直接拿到children,调和,返回workInProgress.child
function updateHostComponent(current, workInProgress) {
const nextProps = workInProgress.pendingProps;
// newChildren其实就是新的子虚你dom节点。
const nextChildren = nextProps.children;
// 调和
reconcileChildren(current, workInProgress, nextChildren)
return workInProgress.child;
}
// 可见updateXXXComponent的目的都是一样的:拿到新的虚拟dom,调和,最后返回workInProgress.child
// 所以reconcileChildren应该用新的虚拟dom做了一些事,同时将workInProgress.child赋值
// 调和子节点就是用最新的虚拟dom去更新旧fiber对象的过程
// 因为初次渲染过程是没有旧的渲染对象的,所以都是新建fiber对象
function reconcileChildren(current: Fiber, workInProgress: Fiber, nextChildren: any) {
if (current === null) {
// mountChildFiber的返回值会赋值给workInprogress.child
// mountChildFiber本身是工厂函数创建的,真正实现是reconcileChildFibers
workInProgress.child = mountChildFibers(workInProgress, null, nextChildren)
}
}
function reconcileChildFibers(returnFiber, currentFirstChild, newChild) {
if (typeof newChild === 'object' && newChild !== null) {
// 子虚拟dom是单个元素的情况
switch(newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
// 先看reconcileSingleElement,暂时忽略placeSingleChild
return placeSingleChild(
reconcileSingleElement(returnFibler, currentFirstChild, newChild)
);
}
// 子虚你dom是一个数组的情况
if (isArray(newChild)) {
return reconcileChildArray(returnFiber, currentFirstChild, newChild);
}
}
}
function reconcileSingleElement(returnFiber, currentFirstChild, element) {
// 创建一个fiber对象
const created = createFiberFromElement(element, returnFiber.mode);
// return属性指向returnFiber,即:workInProgress
created.return = returnFiber;
return created;
}
function reconcileChildArray(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChildren: Array<*>
): Fiber | null {
let oldFiber = currentFirstChild;
let newIdx = 0;
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
// 省略,因为初始化渲染时,oldFiber是null
}
if (oldFiber === null) {
// 保存第一个fiber对象
let resultingFirstChild;
// 前一个fiber对象,用于设置sibling
let previousNewFiber = null;
for (; newIdx < newChildren.length; newIdx++) {
// for循序,为每个虚拟dom新建对应的fiber对象
const newFiber = createFiber(returnFiber, newChildren[newIdx]);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
// 使用sibling链接下一个fiber节点
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
// 返回第一个fiber对象
return resultingFirstChild;
}
// 省略其他代码
}
// 不管是单个虚拟dom,还是虚拟dom数组,reconcileChildren都会创建对应的fiber节点
// fiber节点之间由return child sibling互相引用。
目前可以得出fiberRoot、current、workInProgress的引用关系:
FiberRootNode
| ^
| |
workInProgress current stateNode
| | |
V V |
hostRootFiber <-- alternate --> hostRootFiber
| ^
| |
child return
| |
V |
HostComponentFiber(div)<------------------------------+
| ^ ^
| | |
child return return
| | |
V | |
FunctionComponentFiber(FnComp) -- sibling -> ClassComponentFiber(ClazzComp)
| ^ | ^
| | | |
child return child return
| | |. |
V | V |
HostComponentFiber(p) HostComponentFiber(p)
completeWork completeUnitOfWork
// 当`beginWork`返回null时,会转而运行`completeUnitOfWork`
function performUnitOfWork(unitOfWork: Fiber) {
const current = unitOfWork.alternate;
let next = beginWork(current, unitOfWork);
if (next !== null) {
// 之前的逻辑
} else {
// 如果没有child,则调用completeUnitOfWork
completeUnitOfWork(workInProgress)
}
}
/*
控制执行completeWork的流程:
当sibling有值时返回执行beginWork
否则对父fiber节点执行completeWork
*/
function completeUnitOfWork(unitOfWork: Fiber) {
let completedWork = unitOfWork;
do {
const current = completedWork.alternate;
const returnFiber = completedWork.return;
let next = completeWork(current, completedWork);
if (next !== null) {
// completeWork大多数都会返回null,所以可以忽略这里的逻辑
workInProgress = next;
return;
}
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
// 如果有兄弟fiber节点,则函数返回,对兄弟节点调用beginWork
workInProgress = siblingFiber;
return
}
// 对父节点进行completeWork
completedWork = returnFiber;
} while (completedWork !== null)
}
function completeWork(current: Fiber, workInProgress: Fiber) {
const newProps = workInProgress.pendingProps;
// 也是switch...case,同beginWork一样
switch(workInProgress.tag) {
case HostComponent: {
// 创建h1标签(或h2 p标签)
const instance = createInstance(
workInProgress.type,
newProps,
workInProgress,
)
// 如果子fiber也是浏览器组件,那么就挂载到instance下
// 例如将h2和p标签设置为h1的子元素
appendAllChildren(instance, workInProgress);
// 使用stateNode关联
workInProgress.stateNode = instance;
// 设置css属性等等,如果是文字类型的子组件,也一同设置
finilizeInitialChildren(instance, type, newProps);
// 小结:如果是浏览器标签,就会创建对应的标签,设置对应的属性,并将append子标签
return null;
}
case FunctionComponent:
case ClassComponent:
// 没有特别的处理
return null
// 省略其他类型
}
}
// 各个fiber节点执行beginWork completeWork的顺序
函数 fiber.type 对应的组件或标签
1 beginWork HostRoot -
2 beginWork HostComponent div#root
3 beginWork FunctionComponent FnComp
4 beginWork HostComponent p
5 completeWork HostComponent p
6 completeWork FunctionComponent FnComp
7 beginWork ClassComponent ClazzComp
8 beginWork HostComponent p
9 completeWork HostComponent p
10 completeWork ClassComponent ClazzComp
11 completeWork ClassComponent div#root
12 completeWork HostRoot -
小结
- 深度优先的过程
- 从根节点到叶子节点执行beginWork
- 没有叶子节点或所有叶子节点执行完completeWork,则父节点执行completeWork
commit阶段
// render阶段结束后,会进入commit阶段
// finishConcurrentRender调用commitRoot,最终调用commitRootImpl
function commitRootImpl(root: FiberRoot) {
// finishedWork就是render阶段构建的workInProgress树
const finishedWork = root.finishedWork;
// 非初次渲染会执行getSnapshotBeforeUpdate,初次渲染可以忽略
commitBeforeMutationEffects(root, finishedWork);
// 提交修改,更新到页面,初次渲染仅仅是将workInProgress挂载到div#container上
commitMutationEffects(root, finishedWork);
// 将workInProgress树赋值到current
root.current = finishedWork;
// commitMutationEffects之后,所有的dom操作都已经完成,就可以访问dom
commitLayouteEffects(finishedWork, root);
}
commitEffects
// commitMutationEffects调用commitMutationEffectsOnFiber
function commitMutationEffectsOnFiber(finishedWork: Fiber) {
switch(finishedWork.tag) {
case HostComponent: {
// 提交修改操作,重新渲染将在这里设置,初次渲染这里没有进入,可以忽略
recursivelyTraverseMutationEffects(root, finishedWork);
// 首次渲染将finishedWork插入
// todo: 那为什么不放在上面的函数里一起做呢?非要拆成2个函数
commitReconciliationEffects(finishedWork);
}
}
}
function commitReconciliationEffects(finishedWork: Fiber) {
const flags = finishedWork.flags;
// 这里的Placement标签就是上面reconcileChildFibers中的placeSingleChild中设置的
// 如果根节点是类组件、函数组件也都是有地方设置的
// finishedWork就是workInProgress的根,例子中是div#root对应的dom元素
// 初始化渲染,只需要将div#root对应的dom元素挂载到div#container即可
// 因为div#root所有的子节点在render阶段就已经append到根节点上了
if (flags & Placement) {
// 将根节点插入到容器中
commitPlacement(finishedWork);
finishedWork.flags &= ~Placement;
}
}
function commitPlacement(finishedWork: Fiber) {
const parentFiber = getHostParentFiber(finishedWork);
switch(parentFiber.tag) {
case HostRoot: {
// 这个就是div#container
const parent = parentFiber.stateNode.containerInfo;
const before = getHostSibling(finishedWork);
// 将finishedWork.stateNode插入到parent下面,即首次渲染到页面上
insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
}
}
}
function insertOrAppendPlacementNodeIntoContainer(node: Fiber, before, parent) {
const stateNode = node.stateNode;
if (before) {
insertInContainerBefore(parent, stateNode, before);
} else {
appendChildToContainer(parent, stateNode);
}
}
commitLayoutEffects
// commitLayoutEffect最终会调用commitLayoutEffectOnFiber
function commitLayoutEffectOnFiber(
finishedRoot: FiberRoot,
current: Fiber | null,
finishedWork: Fiber
) {
// 如果fiber有LayoutMask,例如:
// 函数组件
// 在调用useLayoutEffect时会在fiber的flags上置HookLayout
// LayoutMask包含HookLayout,所以匹配,最终执行useLayoutEffect中的回调
// 类组件
// 添加了componentDidMount,那么在mountClassInstance函数中置一个标记
// 所以这里同样也是可以匹配上LayoutMask,类组件就会执行componentDidMount函数
if ((finishedWork.flags & LayoutMask) !== NoFlags) {
switch (finishedWork.tag) {
case FunctionComponent: {
// 会调用useLayoutEffect的第一个参数,在Hook中说明
commitHookEffectListMount();
break;
}
case ClassComponent: {
const instance = finishedWork.stateNode;
// LayoutMask包含了Update
if (finishedWork.flags & Update) {
instance.componentDidMount();
}
break;
}
}
}
}
小结
- react将真正提交变化分为3步走
- commitBeforeMutationEffects 所有提交变化之前做的事。本文未涉及
- commitMutationEffects 真正将render阶段的变化应用到页面
- commitLayoutEffects 将修改应用到页面上后做的操作