跳到主要内容

Render 阶段

Render 渲染阶段——初次渲染

1. 创建 hostFiberRoot

// 1. legacyCreateRootFromDOMContainer
// 2. createLegacyRoot
// 3. ReactDOMBlockingRoot
// 4. createRootImpl
// 5. createContainer
// 6. createFiberRoot
export function createFiberRoot(
containerInfo: any,
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks
): FiberRoot {
// 创建 DOM
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
// 创建Fiber树的根节点
const uninitializedFiber = createHostRootFiber(tag);
root.current = uninitializedFiber;
// 根Fiber的真实DOM节点指向fiberRoot;
uninitializedFiber.stateNode = root;
initializeUpdateQueue(uninitializedFiber);
return root;
}
// 7. updateContainer(children, fiberRoot, parentComponent, callback);
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function
): Lane {
enqueueUpdate(current, update); // 挂载更新队列 enqueueUpdate
scheduleUpdateOnFiber(current, lane, eventTime);
}

可以理解为创建根 Fiber 后。表示了当前页面的存在 div#root 的 DOM,接着调用 updateContainer 去对比生成即将渲染的 DOM

2. 创建 workInProgress

// 8. scheduleUpdateOnFiber
// 9. performSyncWorkOnRoot
//10. renderRootSync prepareFreshStack createWorkInProgress workInProgress
export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
// 首次进去不存在
let workInProgress = current.alternate;
if (workInProgress === null) {
// 创建 workInProgress
workInProgress = createFiber(
current.tag,
pendingProps,
current.key,
current.mode
);
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;

workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
workInProgress.pendingProps = pendingProps;
workInProgress.type = current.type;
workInProgress.flags = NoFlags;

workInProgress.nextEffect = null;
workInProgress.firstEffect = null;
workInProgress.lastEffect = null;
}

workInProgress.childLanes = current.childLanes;
workInProgress.lanes = current.lanes;

workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;

workInProgress.sibling = current.sibling;
workInProgress.index = current.index;
workInProgress.ref = current.ref;

return workInProgress;
}

workInProgress

首次渲染不存在 workInProgress,则根据根节点的 Fiber.tag = 3 去创建 workInProgress

//11. workLoopSync
//12. performUnitOfWork
//13.beginWork$1 beginWork
//14 updateHostRoot 此时tag = 3
function updateHostRoot(current, workInProgress, renderLanes) {
const nextProps = workInProgress.pendingProps;
const prevState = workInProgress.memoizedState;
const prevChildren = prevState !== null ? prevState.element : null;
cloneUpdateQueue(current, workInProgress);
processUpdateQueue(workInProgress, nextProps, null, renderLanes);
const nextState = workInProgress.memoizedState;
// Caution: React DevTools currently depends on this property
// being called "element".
const nextChildren = nextState.element;
}
// 15 reconcileChildren

function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
if (current === null) {
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderLanes
);
} else {
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
nextChildren,
renderLanes
);
}
}
// 16 reconcileChildFibers
// 17 reconcileSingleElement
// 18 createFiberFromElement

在 beginwork 中进行并对 workInProgress 挂载 child 返回 performUnitOfWork 中的 next 变量。next 存在则继续循环上述步骤

// next返回A1
// beginWork$1 beginWork
// updateHostComponent 此时tag = 5
// reconcileChildren => mountChildFibers
// reconcileChildrenArray
// createChild 创建新的fiber节点

if (oldFiber === null) {
// If we don't have any more existing children we can choose a fast path
// since the rest will all be insertions.
for (; newIdx < newChildren.length; newIdx++) {
var _newFiber = createChild(returnFiber, newChildren[newIdx], lanes);

if (_newFiber === null) {
continue;
}

lastPlacedIndex = placeChild(_newFiber, lastPlacedIndex, newIdx);

if (previousNewFiber === null) {
// TODO: Move out of the loop. This only happens for the first run.
resultingFirstChild = _newFiber;
} else {
previousNewFiber.sibling = _newFiber;
}

previousNewFiber = _newFiber;
}

return resultingFirstChild;
}

// performUnitOfWork(workInProgress);

生成 A-Fiber 后,对其 children 属性进行判断。存在多个,循环创建 child 并以此 sibling 进行连接

// next返回继续A
// beginwork
// updateHostText -> tryToClaimNextHydratableInstance
// next = null
// 执行completeUnitOfWork(unitOfWork)
// completeWork
// createTextInstance stateNode

备注

执行 completeWork 对 A 文本生成 text 实际 DOM,对其判断是否存在兄弟节点,则 workInProgress 赋值为兄弟 B1

// performUnitOfWork  beginWork$1不存在字节点
// completeUnitOfWork
// completeWork
// createInstance
function createInstance(
type,
props,
rootContainerInstance,
hostContext,
internalInstanceHandle
) {
var parentNamespace;

{
// TODO: take namespace into account when validating.
var hostContextDev = hostContext;
validateDOMNesting(type, null, hostContextDev.ancestorInfo);

if (
typeof props.children === 'string' ||
typeof props.children === 'number'
) {
var string = '' + props.children;
var ownAncestorInfo = updatedAncestorInfo(
hostContextDev.ancestorInfo,
type
);
validateDOMNesting(null, string, ownAncestorInfo);
}

parentNamespace = hostContextDev.namespace;
}

var domElement = createElement(
type,
props,
rootContainerInstance,
parentNamespace
);
precacheFiberNode(internalInstanceHandle, domElement);
updateFiberProps(domElement, props);
return domElement;
}

备注

重复上述步骤一次把 B1 和 B2 的真实 DOM 挂载到 stateNode 完成所有兄弟节点后,取其父子局继续执行该方法生成 A 的真实 DOM 挂载到 stateNode 执行 appendAllChildren 会将文本 A、DivB1、DivB2 添加去 DivA 中

针对 A 生成副作用链表 effectList

此时初次渲染阶段已经完成,即将进入提交阶段commitRoot,欲知后事如何,请听下回分解。

小结

工作流程

根据初次渲染流程,在 Render 阶段的主要工作流程

  • createFiberRoot 创建 Fiber 根节点HostRootFiber与根据入参div#root创建FiberRootNode
  • createWorkInProgress 根据虚拟 DOM 树生成 workInProgressFiber
  • performUnitOfWork 执行单个工作单元
  • beginWork 构建当前 Fiber 的子 Fiber 链表
  • completeUnitOfWork 如果当前 Fiber 没有childFiber子节点,则完成当前工作单元执行,并移动到此 Fiber 的siblingFiber弟弟节点继续执行,如果不存在sibling则,返回returnFiber父节点
  • completeWork 生成当前 Fiber 的真是 DOM 并挂载到stateNode字段。并挂载effectList副作用链表

生成 Fiber 树的规则

1. 根据虚拟DOM深度优先遍历
2. 从顶点开始遍历
3. 生成子节点Fiber树(存在多个用sibling连接)
4. 先子节点 -> 弟弟节点 -> 父节点的弟弟节点
5. 自己没有子节点或者自己的所有子节点遍历完成,则完成当前节点的遍历

Render 渲染阶段——更新渲染

更新阶段根据 currentFiber 和虚拟 DOM 的比对( DOM-DIFF)生成新的 workInProgressFiber

let element = (
<div id='A1' style={style}>
TEXT_A
<div id='B1' style={style}>
TEXT_B1
</div>
<div id='B2-new' style={style}>
<p id='C'>CCC</p>
</div>
<div id='B3' style={style}>
TEXT_B3
</div>
</div>
);

此时我们更新 DOM 为上述内容

1. 双缓存结构

双缓存结构:每次更新复用上次hostFiberRoot.alternate为 workInProgress。

// updateContainer
// scheduleUpdateOnFiber
// performSyncWorkOnRoot workLoopSync performUnitOfWork (currentFiber)
// updateHostRoot(current, workInProgress, renderLanes);
function useFiber(fiber: Fiber, pendingProps: mixed): Fiber {
const clone = createWorkInProgress(fiber, pendingProps);
clone.index = 0;
clone.sibling = null;
return clone;
}
// This is used to create an alternate fiber to do work on.
export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
let workInProgress = current.alternate;
if (workInProgress === null) {
workInProgress = createFiber(
current.tag,
pendingProps,
current.key,
current.mode
);
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
//.../
}

return workInProgress;
}

2. 更新 workInProgress

根据 currentFiber 虚拟 DOM 对比生成新的 workInProgressFiber

节点 A 和 节点 A 文本都可复用,判断是否存在 alternate,不存在则新建 Fiber 并用 alternate 连接。

节点 A 文本的 completeWork 阶段完成,节点 A 上挂载 effectList

节点 B1 和节点 B2 可复用,判断是否存在 alternate,不存在则新建 Fiber 并用 alternate 连接。

节点 B1 completeWork 阶段完成,在节点 A 上挂载 effectList

currentFiber 不存在 C,则新建节点 C,节点 C 有一个 Children 并且是文本,则不去创建 Fiber。

function shouldSetTextContent(type: string, props: Props): boolean {
if (type === 'errorInBeginPhase') {
throw new Error('Error in host config.');
}
return (
typeof props.children === 'string' || typeof props.children === 'number'
);
}

节点 C 的 completeWork 阶段完成,在节点 B2 上挂载 effectList

节点 B2 的 completeWork 阶段完成,在节点 A 上挂载 effectList

节点 B3 的 completeWork 阶段完成,在节点 A 上挂载 effectList

节点 A 的 completeWork 阶段完成,在节点 HostRoot 上挂载 effectList

小结

1. 双缓存结构依靠alternate连接
2. workInProgress生成的过程就是DOM-DIFF
3. 节点只有一个子节点,而且这一个子节点是文本节点的话,不会创建fiber