前言
当前 React 共有三种模式:
- legacy 模式: ReactDOM.render(
element
, rootNode)。这是当前 React app 使用的方式。当前没有计划删除本模式,但是这个模式可能不支持这些新功能。 - blocking 模式: ReactDOM.createBlockingRoot(rootNode).render(
element
)。目前正在实验中。作为迁移到 concurrent 模式的第一个步骤。 - concurrent 模式: ReactDOM.createRoot(rootNode).render(
element
)。目前在实验中,未来稳定之后,打算作为 React 的默认开发模式。这个模式开启了所有的新功能。
React 的 render
阶段开始于 performSyncWorkOnRoot
或者 performConcurrentWorkOnRoot
方法的调用,这主要取决于本次更新是同步更新
还是异步更新
。
performSyncWorkOnRoot & performConcurrentWorkOnRoot
- performSyncWorkOnRoot
// performSyncWorkOnRoot 会调用该方法
// The work loop is an extremely hot path. Tell Closure not to inline it.
/** @noinline */
function workLoopSync() {
// Already timed out, so perform work without checking if we need to yield.
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
可以从这里看到源码
- performConcurrentWorkOnRoot
// performConcurrentWorkOnRoot 会调用该方法
/** @noinline */
function workLoopConcurrent() {
// Perform work until Scheduler asks us to yield
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
可以从这里看到源码
这两种模式唯一的区别是是否调用 shouldYield
。如果当前浏览器帧没有剩余时间,shouldYield 会中止循环,直到浏览器有空闲时间后再继续遍历。
workInProgress
代表当前已经创建的 workInProgress Fiber。
performUnitOfWork
方法会创建下一个 Fiber 节点并赋值给 workInProgress,并将 workInProgress 与已创建的 Fiber 节点连接起来构成 Fiber 树。
React Fiber Reconciler
是从 Stack Reconciler
重构而来,通过遍历的方式实现可中断的递归,所以 performUnitOfWork 的工作可以分为两部分:“递”和“归”
。
“递”阶段
首先从组件树的根节点 rootFiber
开始向下深度优先遍历。为遍历到的每个 Fiber
节点调用 beginWork 方法。
该方法会根据传入的Fiber节点创建子Fiber节点,并将这两个Fiber节点连接起来。
当遍历到叶子节点(即没有子组件的组件)时就会进入“归”阶段。
“归”阶段
在“归”阶段会调用 completeWork 处理 Fiber 节点。
当某个 Fiber 节点执行完 completeWork ,如果其存在兄弟 Fiber 节点(即fiber.sibling !== null),会进入其兄弟 Fiber 的“递”阶段。
如果不存在兄弟 Fiber,会进入父级 Fiber 的“归”阶段。
“递”和“归”阶段会交错执行直到“归”到 rootFiber。至此,render 阶段的工作就结束了。
举例
function App() {
return (
<div className="App">
学习React
<span>源码</span>
</div>
);
}
export default App;
ReactDOM.render(<App />, document.getElementById('root'));
对应的 Fiber树
结构
render
阶段会依次执行:
- rootFiber beginWork
- App Fiber beginWork
- div Fiber beginWork
- 学习React beginWork
- 学习React
completeWork
- span Fiber beginWork
- span Fiber
completeWork
- div Fiber
completeWork
- App Fiber
completeWork
- rootFiber
completeWork
之所以没有 “源码” Fiber 节点的 beginWork/completeWork,是因为作为一种性能优化手段,针对只有单一文本子节点的Fiber,React会特殊处理。
beginWork
可以从 这里 看到 beginWork 源码
beginWork 的工作是传入当前 Fiber节点,创建子 Fiber 节点
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
// ...省略函数体
}
- current:当前组件对应的 Fiber 节点在上一次更新时的 Fiber 节点,即workInProgress.alternate;
- workInProgress:当前组件对应的 Fiber 节点;
- renderLanes:优先级相关;
从 React 双缓存机制
中,除 rootFiber
(应用的根节点) 以外, 组件 mount 时,由于是首次渲染,是不存在当前组件对应的 Fiber 节点在上一次更新时的 Fiber 节点,即 mount 时 current === null。
组件 update 更新时,由于之前已经 mount 过,所以 current !== null。
基于此原因,beginWork 的工作可以分为两部分:
- 组件 mount 时:除
rootFiber
以外,current === null。会根据 fiber.tag 不同,创建不同类型的子 Fiber 节点; - update 时:如果
current
存在,在满足一定条件时可以复用 current 节点,这样就能克隆 current.child 作为 workInProgress.child,而不需要新建 workInProgress.child。
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes
): Fiber | null {
// update时:如果current存在可能存在优化路径,可以复用current(即上一次更新的Fiber节点)
if (current !== null) {
// ...省略代码
// 复用 current
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderLanes,
);
} else {
didReceiveUpdate = false;
}
// mount时:根据tag不同,创建不同的子Fiber节点
switch (workInProgress.tag) {
case IndeterminateComponent:
// ...省略
case LazyComponent:
// ...省略
case FunctionComponent:
// ...省略
case ClassComponent:
// ...省略
case HostRoot:
// ...省略
case HostComponent:
// ...省略
case HostText:
// ...省略
case SuspenseComponent:
// ...省略
// ...省略其他类型
}
}
update 时
满足如下情况时 didReceiveUpdate === false
(复用前一次更新的子 Fiber,不需要新建子Fiber)
oldProps === newProps && workInProgress.type === current.type
,即 props 与 fiber.type 不变;!includesSomeLane(renderLanes, updateLanes)
,即当前 Fiber 节点优先级不够;
if (current !== null) {
// 省略代码...
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;
if (
oldProps !== newProps ||
hasLegacyContextChanged() ||
(__DEV__ ? workInProgress.type !== current.type : false)
) {
didReceiveUpdate = true;
} else if (!includesSomeLane(renderLanes, updateLanes)) {
didReceiveUpdate = false;
switch (workInProgress.tag) {
// 省略代码...
return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
} else {
if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
didReceiveUpdate = true;
} else {
didReceiveUpdate = false;
}
}
} else {
didReceiveUpdate = false;
}
mount 时
不满足优化路径时,我们就进入第二部分,新建子 Fiber。根据 fiber.tag 不同,进入不同类型Fiber 的创建逻辑。
可以从 这里 看到 tag 对应的组件类型
// mount时:根据tag不同,创建不同的子Fiber节点
switch (workInProgress.tag) {
case IndeterminateComponent:
// ...省略
case LazyComponent:
// ...省略
case FunctionComponent:
// ...省略
case ClassComponent:
// ...省略
case HostRoot:
// ...省略
case HostComponent:
// ...省略
case HostText:
// ...省略
case SuspenseComponent:
// ...省略
// ...省略其他类型
}
reconcileChildren
reconcileChildren
是 Reconciler
模块的核心部分。
-
对于
mount
时的组件,它会创建新的子Fiber
节点; -
对于
update
时的组件,它会将当前组件与该组件在上次更新时对应的Fiber
节点比较(也就是俗称的Diff
算法),将比较的结果生成新Fiber
节点
export 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,
);
}
}
可以从 这里 看到 reconcileChildren 源码
通过 current === null
? 来区分组件 mount
阶段与 update
阶段。最终它会生成新的子 Fiber 节点并赋值给 workInProgress.child
,作为本次 beginWork 的 返回值。
reconcileChildFibers
与 mountChildFibers
函数的区别是传入的布尔值不一样。
export const reconcileChildFibers = ChildReconciler(true);
export const mountChildFibers = ChildReconciler(false);
可以从 这里 看到源码
ChildReconciler
方法主要判断是否追踪副作用。
function ChildReconciler(shouldTrackSideEffects) {
function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void {
if (!shouldTrackSideEffects) {
// Noop.
return;
}
const deletions = returnFiber.deletions;
if (deletions === null) {
returnFiber.deletions = [childToDelete];
returnFiber.flags |= ChildDeletion;// 标记 effectTag
} else {
deletions.push(childToDelete);
}
}
...省略代码
}
可以从 这里 看 ChildReconciler 到源码
effectTag
render
阶段的工作是在内存中
进行,当工作结束后会通知 Renderer
需要执行的 DOM操作。要执行DOM操作的具体类型就保存在 fiber.effectTag
中。
你可以从 这里 看到 effectTag 对应的DOM操作
// DOM需要插入到页面中
export const Placement = /* */ 0b00000000000010;
// DOM需要更新
export const Update = /* */ 0b00000000000100;
// DOM需要插入到页面中并更新
export const PlacementAndUpdate = /* */ 0b00000000000110;
// DOM需要删除
export const Deletion = /* */ 0b00000000001000;
....
Renderer
阶段将 Fiber
节点对应的 DOM
节点插入页面中,需要满足两个条件:
-
fiber.stateNode
存在,即 Fiber 节点中保存了对应的 DOM 节点; -
Fiber 节点存在
Placement effectTag
;
fiber.stateNode 会在 completeWork 中创建的,在 mount 时只有 rootFiber
(存在current
) 会赋值 Placement effectTag,在 commit 阶段只会执行一次插入操作。
beginWork 流程图
completeWork
可以从 这里 看到 completeWork 源码
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case IndeterminateComponent:
case LazyComponent:
case SimpleMemoComponent:
case FunctionComponent:
case ForwardRef:
case Fragment:
case Mode:
case Profiler:
case ContextConsumer:
case MemoComponent:
return null;
case ClassComponent: {
// ...省略
return null;
}
case HostRoot: {
// ...省略
updateHostContainer(workInProgress);
return null;
}
case HostComponent: {
// ...省略
return null;
}
// ...省略
我们重点关注页面渲染所必须的 HostComponent
(原生 DOM 组件对应的 Fiber 节点)。
HostComponent
case HostComponent: {
popHostContext(workInProgress);
const rootContainerInstance = getRootHostContainer();
const type = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent(
current,
workInProgress,
type,
newProps,
rootContainerInstance,
);
// update 的情况
// ...省略
} else {
// mount 的情况
// ...省略
}
return null;
}
workInProgress.stateNode
表示 Fiber 节点对应的 DOM 节点。
组件 update 时
当 update 时,Fiber
节点已经存在对应 DOM
节点,所以不需要生成 DOM
节点。需要做的主要是处理props
if (current !== null && workInProgress.stateNode != null) {
// 组件 update 时
updateHostComponent(
current,
workInProgress,
type,
newProps,
rootContainerInstance,
);
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
}
你可以从 这里 看到
updateHostComponent
方法定义。
在 updateHostComponent 内部,被处理完的 props
会被赋值给workInProgress.updateQueue
,并最终会在 commit
阶段被渲染在页面上。
updateHostComponent$1 = function (current, workInProgress, type, newProps, rootContainerInstance) {
var oldProps = current.memoizedProps;
if (oldProps === newProps) {
return;
}
var instance = workInProgress.stateNode;
var currentHostContext = getHostContext();
var updatePayload = prepareUpdate(instance, type, oldProps, newProps, rootContainerInstance, currentHostContext);
workInProgress.updateQueue = updatePayload;
if (updatePayload) {
markUpdate(workInProgress);
}
};
其中 updatePayload
为数组形式,他的偶数索引的值为变化的prop key,奇数索引的值为变化的prop value。
组件 mount 时
mount 时的主要逻辑包括三个:
为 Fiber 节点生成对应的 DOM 节点
;将子孙 DOM 节点插入刚生成的 DOM 节点中
;与update 逻辑中的 updateHostComponent 类似的处理 props 的过程
;
const currentHostContext = getHostContext();
if(wasHydrated){
...
}else {
// 为fiber创建对应DOM节点
const instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);
// 将子孙DOM节点插入刚生成的DOM节点中
appendAllChildren(instance, workInProgress, false, false);
// DOM节点赋值给fiber.stateNode
workInProgress.stateNode = instance;
// 与update逻辑中的updateHostComponent类似的处理props的过程
if (
finalizeInitialChildren(
instance,
type,
newProps,
rootContainerInstance,
currentHostContext,
)
) {
markUpdate(workInProgress);
}
}
mount 时只会在 rootFiber
存在 Placement effectTag
。那么 commit 阶段是如何通过一次插入DOM操作(对应一个Placement effectTag)将整棵DOM树插入页面的呢?
原因在于 completeWork
中的 appendAllChildren
方法。
由于 completeWork 属于“归”阶段调用的函数,每次调用 appendAllChildren 时都会将已生成的子孙 DOM 节点插入当前生成的 DOM 节点下。那么当“归”到 rootFiber 时,我们已经有一个构建好的离屏 DOM
树。
effectList
作为 DOM 操作的依据,在 commit
阶段需要找到所有有 effectTag
的 Fiber
节点并依次执行 effectTag
对应操作。如果在 commit 阶段再遍历一次 Fiber 树寻找 effectTag !== null
的Fiber节点显然很低效。
为了解决这个问题,在 completeWork 的上层函数 completeUnitOfWork
中,每个执行完 completeWork 且存在 effectTag 的 Fiber 节点会被保存在一条被称为 effectList 的单向链表
中。
effectList
中第一个 Fiber 节点保存在 fiber.firstEffect
,最后一个元素保存在fiber.lastEffect
。
类似 appendAllChildren
,在“归”阶段,所有有 effectTag 的 Fiber 节点都会被追加在 effectList 中,最终形成一条以 rootFiber.firstEffect 为起点的单向链表。
nextEffect nextEffect
rootFiber.firstEffect -----------> fiber -----------> fiber
在 commit 阶段只需要遍历 effectList 就能执行所有 effect 了。
你可以在 这里 看到 completeUnitOfWork
代码逻辑。
流程结尾
至此,render 阶段全部工作完成。在 performSyncWorkOnRoot
函数中 fiberRootNode 被传递给 commitRoot 方法,开启 commit 阶段工作流程。
const finishedWork: Fiber = (root.current.alternate: any);
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
commitRoot(root);
completeWork 流程图
整个渲染流程
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
num: 0,
};
}
updateNum() {
const { num } = this.state;
this.setState({ num: num + 1 });
}
render() {
return (
<div className="App">
<div onClick={this.updateNum.bind(this)}>学习 React--num:{this.state.num}</div>
<span>源码</span>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
以上面 class 组件为例子
-
rootFiber beginWork
-
App Fiber beginWork
-
className 为"App" 的 div Fiber beginWork
-
存在 onClick 事件的 div Fiber beginWork
-
“学习 React--num:” Fiber beginWork
-
“学习 React--num:” Fiber
completeWork
-
num 为 0 Fiber beginWork
-
num 为 0 Fiber
completeWork
-
存在 onClick 事件的 div Fiber
completeWork
-
span Fiber beginWork
-
span Fiber
completeWork
-
className 为"App" 的 div Fiber
completeWork
-
App Fiber
completeWork
-
rootFiber
completeWork
组件 mount 阶段
我们知道 render 阶段首先会执行 beginWork
方法,该方法只会返回一个子 fiber 节点
,以上面的测试 Demo 为例来看看主要流程。
首先进入 beginWork 是当前应用的根节点 rootFiber
。根据fiber 双缓存原理,此时 存在tag
为 3 的 HostRoot
的 current fiber
以及 workInProgress
。
第二个进入 beginWork 的是 tag 为 1 的 App
ClassComponent , 它没有 current fiber , 只有 workInProgress fiber。
第三个进入 beginWork 的是 tag 为 5 的 div
HostComponent , 它也没有 current fiber , 只有 workInProgress fiber。
剩下的省略.....
以 div
HostComponent 为例,研究它 mount 阶段的主要过程。
beginWork
首先进入 beginWork ,根据 workInProgress.tag 进入 updateHostComponent;
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
....
switch (workInProgress.tag) {
.....
case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes);
.....
}
...
}
在 updateHostComponent 方法中,var isDirectTextChild = shouldSetTextContent(type, nextProps);
是判断当前的 fiber 是否只有唯一的文本子节点,如果是的话不会为它创建一个子 fiber 节点,这是一种优化手段
。
function updateHostComponent(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
) {
pushHostContext(workInProgress);
if (current === null) {
tryToClaimNextHydratableInstance(workInProgress);
}
const type = workInProgress.type;
const nextProps = workInProgress.pendingProps;
const prevProps = current !== null ? current.memoizedProps : null;
let nextChildren = nextProps.children;
const isDirectTextChild = shouldSetTextContent(type, nextProps);
if (isDirectTextChild) {
nextChildren = null;
} else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
workInProgress.flags |= ContentReset;
}
markRef(current, workInProgress);
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
在 updateHostComponent 会调用 reconcileChildren
方法会为当前 workInProgress 创建子 fiber 节点。
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
在 reconcileChildren 方法中,根据 current fiber
是否存在进入不同的处理逻辑。当前是 mount 阶段不存在 current fiber,所以进入 mountChildFibers
方法中。
export 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,
);
}
}
export const reconcileChildFibers = ChildReconciler(true);
export const mountChildFibers = ChildReconciler(false);
mountChildFibers
和 reconcileChildFibers
都是通过 ChildReconciler 方法传入不同的布尔值得到的。
ChildReconciler 方法返回 reconcileChildFibers
方法。
function ChildReconciler(shouldTrackSideEffects) {
function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void {
if (!shouldTrackSideEffects) {
// Noop.
return;
}
const deletions = returnFiber.deletions;
if (deletions === null) {
returnFiber.deletions = [childToDelete];
returnFiber.flags |= ChildDeletion;
} else {
deletions.push(childToDelete);
}
}
....
return reconcileChildFibers;
}
shouldTrackSideEffects
表示是否追踪副作用,如果不追踪则直接返回,如果追踪则会打上标记。
reconcileChildFibers
在 reconcileChildFibers 方法中通过判断不同的 newChild
的类型进入不同的处理逻辑。当前 newChild 为数组,则进入 reconcileChildrenArray
的处理逻辑。
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
lanes: Lanes,
): Fiber | null {
const isUnkeyedTopLevelFragment =
typeof newChild === 'object' &&
newChild !== null &&
newChild.type === REACT_FRAGMENT_TYPE &&
newChild.key === null;
if (isUnkeyedTopLevelFragment) {
newChild = newChild.props.children;
}
if (typeof newChild === 'object' && newChild !== null) {
....
if (isArray(newChild)) {
return reconcileChildrenArray(
returnFiber,
currentFirstChild,
newChild,
lanes,
);
}
.....
return deleteRemainingChildren(returnFiber, currentFirstChild);
}
reconcileChildrenArray
在 reconcileChildrenArray 方法中会创建 newFiber
节点,但只会创建一个。
通过上图中我们知道会调用 createChild
方法。
但只会创建一个 fiber 节点
createChild
createChild
方法会根据 newChild
不同的类型进入不同的处理逻辑。
createFiberFromElement
createFiberFromElement
方法会调用 createFiberFromTypeAndProps
并且返回 fiber
。
export 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
然后会调用 createFiber
方法创建 fiber 节点。
createFiber
var createFiber = function (tag, pendingProps, key, mode) {
return new FiberNode(tag, pendingProps, key, mode);
};
FiberNode
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
// Instance
this.tag = tag;
this.key = key;
this.elementType = null;
this.type = null;
this.stateNode = null;
// Fiber
this.return = null;
this.child = null;
this.sibling = null;
this.index = 0;
this.ref = null;
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;
this.mode = mode;
// Effects
this.flags = NoFlags;
this.subtreeFlags = NoFlags;
this.deletions = null;
this.lanes = NoLanes;
this.childLanes = NoLanes;
this.alternate = null;
}
“递” 阶段的调用栈
completeWork
首先进入 completeWork 的是 tag
为 6
的 HostText
“学习 React--num:” 文本 fiber 节点 ; 第二个进入 completeWork 同样是 tag 为 6 的是 “ 0 ” 文本 fiber 节点 ; 第三个进入 completeWork 的是 tag 为 5 ,elementType 为 "div"
,存在 onClick 事件的 HostComponent
,我们以该 fiber 节点为例。
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) {
updateHostComponent(
current,
workInProgress,
type,
newProps,
rootContainerInstance,
);
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
} else {
if (!newProps) {
bubbleProperties(workInProgress);
return null;
}
const currentHostContext = getHostContext();
if (wasHydrated) {
.....
}
} else {
const instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);
appendAllChildren(instance, workInProgress, false, false);
workInProgress.stateNode = instance;
if (
finalizeInitialChildren(
instance,
type,
newProps,
rootContainerInstance,
currentHostContext,
)
) {
markUpdate(workInProgress);
}
}
if (workInProgress.ref !== null) {
markRef(workInProgress);
}
}
bubbleProperties(workInProgress);
return null;
}
...
}
}
createInstance
createInstance
方法会创建对应的 DOM
节点 。
const instance = createInstance(type,newProps,rootContainerInstance,
currentHostContext,
workInProgress);
createElement
在 createInstance 方法中会调用 createElement
方法创建 DOM 节点。
function createElement(type, props, rootContainerElement, parentNamespace) {
var isCustomComponentTag;
var ownerDocument = getOwnerDocumentFromRootContainer(rootContainerElement);
var domElement;
var namespaceURI = parentNamespace;
if (namespaceURI === HTML_NAMESPACE) {
namespaceURI = getIntrinsicNamespace(type);
}
if (namespaceURI === HTML_NAMESPACE) {
{
isCustomComponentTag = isCustomComponent(type, props); // Should this check be gated by parent namespace? Not sure we want to
// allow <SVG> or <mATH>.
if (!isCustomComponentTag && type !== type.toLowerCase()) {
error('<%s /> is using incorrect casing. ' + 'Use PascalCase for React components, ' + 'or lowercase for HTML elements.', type);
}
}
if (type === 'script') {
// Create the script via .innerHTML so its "parser-inserted" flag is
// set to true and it does not execute
var div = ownerDocument.createElement('div');
div.innerHTML = '<script><' + '/script>'; // eslint-disable-line
// This is guaranteed to yield a script element.
var firstChild = div.firstChild;
domElement = div.removeChild(firstChild);
} else if (typeof props.is === 'string') {
// $FlowIssue `createElement` should be updated for Web Components
domElement = ownerDocument.createElement(type, {
is: props.is
});
} else {
domElement = ownerDocument.createElement(type);
if (type === 'select') {
var node = domElement;
if (props.multiple) {
node.multiple = true;
} else if (props.size) {
node.size = props.size;
}
}
}
} else {
domElement = ownerDocument.createElementNS(namespaceURI, type);
}
{
if (namespaceURI === HTML_NAMESPACE) {
if (!isCustomComponentTag && Object.prototype.toString.call(domElement) === '[object HTMLUnknownElement]' && !hasOwnProperty.call(warnedUnknownTags, type)) {
warnedUnknownTags[type] = true;
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);
}
}
}
return domElement;
}
appendAllChildren
appendAllChildren
方法是将已经创建好的 DOM 插入之前已经创建好的 DOM 树中。
var instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress);
appendAllChildren(instance, workInProgress, false, false);
workInProgress.stateNode = instance;
if (finalizeInitialChildren(instance, type, newProps, rootContainerInstance)) {
markUpdate(workInProgress);
}
workInProgress.stateNode = instance;
将创建好的 DOM 节点赋值给 workInProgress fiber 的 stateNode。
finalizeInitialChildren
finalizeInitialChildren
方法为 DOM 节点设置属性。
performSyncWorkOnRoot
当所有的节点都执行完 completeWork 时,会进入 performSyncWorkOnRoot
的其它逻辑。
function performSyncWorkOnRoot(root) {
....
var finishedWork = root.current.alternate;
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
commitRoot(root); // 进入 commit 阶段
ensureRootIsScheduled(root, now());
return null;
}
root.current.alternate
指向当前本次更新创建的 workInProgress fiber 节点。
组件 update 阶段
当我们点击 div
标签改变 num 的值时,组件就进入 update 阶段。我们还是以
<div onClick={this.updateNum.bind(this)}> 学习 React--num:{this.state.num}</div>
div 标签为例。
beginWork
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
...
if (current !== null) {
var oldProps = current.memoizedProps;
var newProps = workInProgress.pendingProps;
if (oldProps !== newProps || hasContextChanged() || ( workInProgress.type !== current.type )) {
didReceiveUpdate = true;
} ....
} else {
didReceiveUpdate = false;
}
workInProgress.lanes = NoLanes;
switch (workInProgress.tag) {
...
case HostComponent:
return updateHostComponent(current, workInProgress, renderLanes);
...
}
....
}
在组件更新阶段此时已经存在 current fiber , 通过条件判断设置 didReceiveUpdate = true
, 紧接着进入 updateHostComponent
逻辑。
updateHostComponent
function updateHostComponent(current, workInProgress, renderLanes) {
.....
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
在 updateHostComponent 方法中会调用 reconcileChildren
;
reconcileChildren
reconcileChildren 方法通过 current fiber
和 nextChildren
进行对比,将对比的结果创建一个新的 fiber 节点并返回。
completeWork
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderLanes: Lanes,
): Fiber | null {
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
...
case HostComponent: {
....
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent$1(
current,
workInProgress,
type,
newProps,
rootContainerInstance,
);
.....
}
....
}
.....
}
updateHostComponent$1
updateHostComponent$1 = function (current, workInProgress, type, newProps, rootContainerInstance) {
.....
var updatePayload = prepareUpdate(instance, type, oldProps, newProps, rootContainerInstance, currentHostContext);
workInProgress.updateQueue = updatePayload;
if (updatePayload) {
markUpdate(workInProgress);
}
};
updateHostComponent$1
方法中最重要的是 prepareUpdate 方法,prepareUpdate
方法跟属性的改变有关。
prepareUpdate
function prepareUpdate(domElement, type, oldProps, newProps, rootContainerInstance, hostContext) {
{
.....
return diffProperties(domElement, type, oldProps, newProps);
}
diffProperties
function diffProperties(domElement, tag, lastRawProps, nextRawProps, rootContainerElement) {
.....
return updatePayload;
}
diffProperties 方法最后返回 updatePayload
, 然后赋值给workInProgress.updateQueue。
自此,render 阶段的 update 主要过程就完了。