React 16 版本中初始渲染的流程
// 更新 hostRoot
// <div id="root"></div> 对应的 Fiber 对象
function updateHostRoot(current, workInProgress, renderExpirationTime) {
pushHostRootContext(workInProgress);
// 获取更新队列
const updateQueue = workInProgress.updateQueue;
invariant(
current !== null && updateQueue !== null,
'If the root does not have an updateQueue, we should have already ' +
'bailed out. This error is likely caused by a bug in React. Please ' +
'file an issue.',
);
// 获取新的 props 对象 null
const nextProps = workInProgress.pendingProps;
// 获取上一次渲染使用的 state null
const prevState = workInProgress.memoizedState;
// 获取上一次渲染使用的 children null
const prevChildren = prevState !== null ? prevState.element : null;
// 浅复制更新队列, 防止引用属性互相影响
// workInProgress.updateQueue 浅拷贝 current.updateQueue
cloneUpdateQueue(current, workInProgress);
// 获取 updateQueue.payload 并赋值到 workInProgress.memoizedState
// 要更新的内容就是 element 就是 rootFiber 的子元素
processUpdateQueue(workInProgress, nextProps, null, renderExpirationTime);
// 获取 element 所在对象
const nextState = workInProgress.memoizedState;
// 从对象中获取 element
const nextChildren = nextState.element;
// 在计算 state 后如果前后两个 Children 相同的情况
// prevChildren => null
// nextState => App
// false
if (nextChildren === prevChildren) {
// If the state is the same as before, that's a bailout because we had
// no work that expires at this time.
resetHydrationState();
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
// 获取 fiberRoot 对象
const root: FiberRoot = workInProgress.stateNode;
// 服务器端渲染走 if
if (root.hydrate && enterHydrationState(workInProgress)) {
// If we don't have any current children this might be the first pass.
// We always try to hydrate. If this isn't a hydration pass there won't
// be any children to hydrate which is effectively the same thing as
// not hydrating.
let child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderExpirationTime,
);
workInProgress.child = child;
let node = child;
while (node) {
// Mark each child as hydrating. This is a fast path to know whether this
// tree is part of a hydrating tree. This is used to determine if a child
// node has fully mounted yet, and for scheduling event replaying.
// Conceptually this is similar to Placement in that a new subtree is
// inserted into the React tree here. It just happens to not need DOM
// mutations because it already exists.
node.effectTag = (node.effectTag & ~Placement) | Hydrating;
node = node.sibling;
}
} else {
// 客户端渲染走 else
// 构建子节点 fiber 对象
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
resetHydrationState();
}
// 返回子节点 fiber 对象
return workInProgress.child;
}
// 构建子级 Fiber 对象
export function reconcileChildren(
// 旧 Fiber
current: Fiber | null,
// 父级 Fiber
workInProgress: Fiber,
// 子级 vdom 对象
nextChildren: any,
// 初始渲染 整型最大值 代表同步任务
renderExpirationTime: ExpirationTime,
) {
/**
* 为什么要传递 current ?
* 如果不是初始渲染的情况, 要进行新旧 Fiber 对比
* 初始渲染时则用不到 current
*/
// 如果就 Fiber 为 null 表示初始渲染
if (current === null) {
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderExpirationTime,
);
} else {
// 否则就是更新
// If the current child is the same as the work in progress, it means that
// we haven't yet started any work on these children. Therefore, we use
// the clone algorithm to create a copy of all the current children.
// If we had any progressed work already, that is invalid at this point so
// let's throw it out.
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
nextChildren,
renderExpirationTime,
);
}
}
// 用于初始渲染
export const mountChildFibers = ChildReconciler(false);
// This wrapper function exists because I expect to clone the code in each path
// to be able to optimize each path individually by branching early. This needs
// a compiler or we can do it manually. Helpers that don't need this branching
// live outside of this function.
function ChildReconciler(shouldTrackSideEffects) {
function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void {
if (!shouldTrackSideEffects) {
// Noop.
return;
}
// Deletions are added in reversed order so we add it to the front.
// At this point, the return fiber's effect list is empty except for
// deletions, so we can just append the deletion to the list. The remaining
// effects aren't added until the complete phase. Once we implement
// resuming, this may not be true.
const last = returnFiber.lastEffect;
if (last !== null) {
last.nextEffect = childToDelete;
returnFiber.lastEffect = childToDelete;
} else {
returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
}
childToDelete.nextEffect = null;
childToDelete.effectTag = Deletion;
}
function deleteRemainingChildren(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
): null {
if (!shouldTrackSideEffects) {
// Noop.
return null;
}
// TODO: For the shouldClone case, this could be micro-optimized a bit by
// assuming that after the first child we've already added everything.
let childToDelete = currentFirstChild;
while (childToDelete !== null) {
deleteChild(returnFiber, childToDelete);
childToDelete = childToDelete.sibling;
}
return null;
}
function mapRemainingChildren(
returnFiber: Fiber,
currentFirstChild: Fiber,
): Map<string | number, Fiber> {
// Add the remaining children to a temporary map so that we can find them by
// keys quickly. Implicit (null) keys get added to this set with their index
// instead.
const existingChildren: Map<string | number, Fiber> = new Map();
let existingChild = currentFirstChild;
while (existingChild !== null) {
if (existingChild.key !== null) {
existingChildren.set(existingChild.key, existingChild);
} else {
existingChildren.set(existingChild.index, existingChild);
}
existingChild = existingChild.sibling;
}
return existingChildren;
}
function useFiber(fiber: Fiber, pendingProps: mixed): Fiber {
// We currently set sibling to null and index to 0 here because it is easy
// to forget to do before returning it. E.g. for the single child case.
const clone = createWorkInProgress(fiber, pendingProps);
clone.index = 0;
clone.sibling = null;
return clone;
}
function placeChild(
newFiber: Fiber,
lastPlacedIndex: number,
newIndex: number,
): number {
newFiber.index = newIndex;
if (!shouldTrackSideEffects) {
// Noop.
return lastPlacedIndex;
}
const current = newFiber.alternate;
if (current !== null) {
const oldIndex = current.index;
if (oldIndex < lastPlacedIndex) {
// This is a move.
newFiber.effectTag = Placement;
return lastPlacedIndex;
} else {
// This item can stay in place.
return oldIndex;
}
} else {
// This is an insertion.
newFiber.effectTag = Placement;
return lastPlacedIndex;
}
}
function placeSingleChild(newFiber: Fiber): Fiber {
// 如果是初始渲染 会在根组件(App)上设置 effectTag 属性为 Placement 值为 1
// 其他子级节点具有默认值为 0 防止在 commit 阶段反复操作真实DOM
// 初始渲染时如果当前处理的是根组件 true 其他组件 false
if (shouldTrackSideEffects && newFiber.alternate === null) {
// Placement 表示新创建的节点
newFiber.effectTag = Placement;
}
return newFiber;
}
function updateTextNode(
returnFiber: Fiber,
current: Fiber | null,
textContent: string,
expirationTime: ExpirationTime,
) {
if (current === null || current.tag !== HostText) {
// Insert
const created = createFiberFromText(
textContent,
returnFiber.mode,
expirationTime,
);
created.return = returnFiber;
return created;
} else {
// Update
const existing = useFiber(current, textContent);
existing.return = returnFiber;
return existing;
}
}
function updateElement(
returnFiber: Fiber,
current: Fiber | null,
element: ReactElement,
expirationTime: ExpirationTime,
): Fiber {
if (current !== null) {
if (
current.elementType === element.type ||
// Keep this check inline so it only runs on the false path:
(__DEV__ ? isCompatibleFamilyForHotReloading(current, element) : false)
) {
// Move based on index
const existing = useFiber(current, element.props);
existing.ref = coerceRef(returnFiber, current, element);
existing.return = returnFiber;
if (__DEV__) {
existing._debugSource = element._source;
existing._debugOwner = element._owner;
}
return existing;
} else if (
enableBlocksAPI &&
current.tag === Block &&
element.type.$$typeof === REACT_BLOCK_TYPE &&
element.type.render === current.type.render
) {
// Same as above but also update the .type field.
const existing = useFiber(current, element.props);
existing.return = returnFiber;
existing.type = element.type;
if (__DEV__) {
existing._debugSource = element._source;
existing._debugOwner = element._owner;
}
return existing;
}
}
// 创建 Fiber 对象
const created = createFiberFromElement(
element,
returnFiber.mode,
expirationTime,
);
// 添加 ref 属性
created.ref = coerceRef(returnFiber, current, element);
// 添加父级 Fiber 对象
created.return = returnFiber;
// 返回创建的 Fiber 对象
return created;
}
function updatePortal(
returnFiber: Fiber,
current: Fiber | null,
portal: ReactPortal,
expirationTime: ExpirationTime,
): Fiber {
if (
current === null ||
current.tag !== HostPortal ||
current.stateNode.containerInfo !== portal.containerInfo ||
current.stateNode.implementation !== portal.implementation
) {
// Insert
const created = createFiberFromPortal(
portal,
returnFiber.mode,
expirationTime,
);
created.return = returnFiber;
return created;
} else {
// Update
const existing = useFiber(current, portal.children || []);
existing.return = returnFiber;
return existing;
}
}
function updateFragment(
returnFiber: Fiber,
current: Fiber | null,
fragment: Iterable<*>,
expirationTime: ExpirationTime,
key: null | string,
): Fiber {
if (current === null || current.tag !== Fragment) {
// Insert
const created = createFiberFromFragment(
fragment,
returnFiber.mode,
expirationTime,
key,
);
created.return = returnFiber;
return created;
} else {
// Update
const existing = useFiber(current, fragment);
existing.return = returnFiber;
return existing;
}
}
function createChild(
returnFiber: Fiber,
newChild: any,
expirationTime: ExpirationTime,
): Fiber | null {
if (typeof newChild === 'string' || typeof newChild === 'number') {
// Text nodes don't have keys. If the previous node is implicitly keyed
// we can continue to replace it without aborting even if it is not a text
// node.
const created = createFiberFromText(
'' + newChild,
returnFiber.mode,
expirationTime,
);
created.return = returnFiber;
return created;
}
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
const created = createFiberFromElement(
newChild,
returnFiber.mode,
expirationTime,
);
created.ref = coerceRef(returnFiber, null, newChild);
created.return = returnFiber;
return created;
}
case REACT_PORTAL_TYPE: {
const created = createFiberFromPortal(
newChild,
returnFiber.mode,
expirationTime,
);
created.return = returnFiber;
return created;
}
}
if (isArray(newChild) || getIteratorFn(newChild)) {
const created = createFiberFromFragment(
newChild,
returnFiber.mode,
expirationTime,
null,
);
created.return = returnFiber;
return created;
}
throwOnInvalidObjectType(returnFiber, newChild);
}
if (__DEV__) {
if (typeof newChild === 'function') {
warnOnFunctionType();
}
}
return null;
}
function updateSlot(
returnFiber: Fiber,
oldFiber: Fiber | null,
newChild: any,
expirationTime: ExpirationTime,
): Fiber | null {
// Update the fiber if the keys match, otherwise return null.
const key = oldFiber !== null ? oldFiber.key : null;
if (typeof newChild === 'string' || typeof newChild === 'number') {
// Text nodes don't have keys. If the previous node is implicitly keyed
// we can continue to replace it without aborting even if it is not a text
// node.
if (key !== null) {
return null;
}
return updateTextNode(
returnFiber,
oldFiber,
'' + newChild,
expirationTime,
);
}
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
if (newChild.key === key) {
if (newChild.type === REACT_FRAGMENT_TYPE) {
return updateFragment(
returnFiber,
oldFiber,
newChild.props.children,
expirationTime,
key,
);
}
return updateElement(
returnFiber,
oldFiber,
newChild,
expirationTime,
);
} else {
return null;
}
}
case REACT_PORTAL_TYPE: {
if (newChild.key === key) {
return updatePortal(
returnFiber,
oldFiber,
newChild,
expirationTime,
);
} else {
return null;
}
}
}
if (isArray(newChild) || getIteratorFn(newChild)) {
if (key !== null) {
return null;
}
return updateFragment(
returnFiber,
oldFiber,
newChild,
expirationTime,
null,
);
}
throwOnInvalidObjectType(returnFiber, newChild);
}
if (__DEV__) {
if (typeof newChild === 'function') {
warnOnFunctionType();
}
}
return null;
}
function updateFromMap(
existingChildren: Map<string | number, Fiber>,
returnFiber: Fiber,
newIdx: number,
newChild: any,
expirationTime: ExpirationTime,
): Fiber | null {
if (typeof newChild === 'string' || typeof newChild === 'number') {
// Text nodes don't have keys, so we neither have to check the old nor
// new node for the key. If both are text nodes, they match.
const matchedFiber = existingChildren.get(newIdx) || null;
return updateTextNode(
returnFiber,
matchedFiber,
'' + newChild,
expirationTime,
);
}
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
const matchedFiber =
existingChildren.get(
newChild.key === null ? newIdx : newChild.key,
) || null;
if (newChild.type === REACT_FRAGMENT_TYPE) {
return updateFragment(
returnFiber,
matchedFiber,
newChild.props.children,
expirationTime,
newChild.key,
);
}
return updateElement(
returnFiber,
matchedFiber,
newChild,
expirationTime,
);
}
case REACT_PORTAL_TYPE: {
const matchedFiber =
existingChildren.get(
newChild.key === null ? newIdx : newChild.key,
) || null;
return updatePortal(
returnFiber,
matchedFiber,
newChild,
expirationTime,
);
}
}
if (isArray(newChild) || getIteratorFn(newChild)) {
const matchedFiber = existingChildren.get(newIdx) || null;
return updateFragment(
returnFiber,
matchedFiber,
newChild,
expirationTime,
null,
);
}
throwOnInvalidObjectType(returnFiber, newChild);
}
if (__DEV__) {
if (typeof newChild === 'function') {
warnOnFunctionType();
}
}
return null;
}
/**
* Warns if there is a duplicate or missing key
*/
function warnOnInvalidKey(
child: mixed,
knownKeys: Set<string> | null,
): Set<string> | null {
if (__DEV__) {
if (typeof child !== 'object' || child === null) {
return knownKeys;
}
switch (child.$$typeof) {
case REACT_ELEMENT_TYPE:
case REACT_PORTAL_TYPE:
warnForMissingKey(child);
const key = child.key;
if (typeof key !== 'string') {
break;
}
if (knownKeys === null) {
knownKeys = new Set();
knownKeys.add(key);
break;
}
if (!knownKeys.has(key)) {
knownKeys.add(key);
break;
}
console.error(
'Encountered two children with the same key, `%s`. ' +
'Keys should be unique so that components maintain their identity ' +
'across updates. Non-unique keys may cause children to be ' +
'duplicated and/or omitted — the behavior is unsupported and ' +
'could change in a future version.',
key,
);
break;
default:
break;
}
}
return knownKeys;
}
// 处理子元素是数组的情况
function reconcileChildrenArray(
// 父级 Fiber
returnFiber: Fiber,
currentFirstChild: Fiber | null,
// 子级 vdom 数组
newChildren: Array<*>,
expirationTime: ExpirationTime,
): Fiber | null {
if (__DEV__) {
// First, validate keys.
let knownKeys = null;
for (let i = 0; i < newChildren.length; i++) {
const child = newChildren[i];
knownKeys = warnOnInvalidKey(child, knownKeys);
}
}
/**
* 存储第一个子节点 Fiber 对象
* 方法返回的也是第一个子节点 Fiber 对象
* 因为其他子节点 Fiber 对象都存储在上一个子 Fiber 节点对象的 sibling 属性中
*/
let resultingFirstChild: Fiber | null = null;
// 上一次创建的 Fiber 对象
let previousNewFiber: Fiber | null = null;
// 初始渲染没有旧的子级 所以为 null
let oldFiber = currentFirstChild;
let lastPlacedIndex = 0;
let newIdx = 0;
let nextOldFiber = null;
// 初始渲染 oldFiber 为 null 循环不执行
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
if (oldFiber.index > newIdx) {
nextOldFiber = oldFiber;
oldFiber = null;
} else {
nextOldFiber = oldFiber.sibling;
}
const newFiber = updateSlot(
returnFiber,
oldFiber,
newChildren[newIdx],
expirationTime,
);
if (newFiber === null) {
// TODO: This breaks on empty slots like null children. That's
// unfortunate because it triggers the slow path all the time. We need
// a better way to communicate whether this was a miss or null,
// boolean, undefined, etc.
if (oldFiber === null) {
oldFiber = nextOldFiber;
}
break;
}
if (shouldTrackSideEffects) {
if (oldFiber && newFiber.alternate === null) {
// We matched the slot, but we didn't reuse the existing fiber, so we
// need to delete the existing child.
deleteChild(returnFiber, oldFiber);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
// TODO: Move out of the loop. This only happens for the first run.
resultingFirstChild = newFiber;
} else {
// TODO: Defer siblings if we're not at the right index for this slot.
// I.e. if we had null values before, then we want to defer this
// for each null value. However, we also don't want to call updateSlot
// with the previous one.
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
oldFiber = nextOldFiber;
}
// 初始渲染不执行
if (newIdx === newChildren.length) {
// We've reached the end of the new children. We can delete the rest.
deleteRemainingChildren(returnFiber, oldFiber);
return resultingFirstChild;
}
// oldFiber 为空 说明是初始渲染
if (oldFiber === null) {
// 遍历子 vdom 对象
for (; newIdx < newChildren.length; newIdx++) {
// 创建子 vdom 对应的 fiber 对象
const newFiber = createChild(
returnFiber,
newChildren[newIdx],
expirationTime,
);
// 如果 newFiber 为 null
if (newFiber === null) {
// 进入下次循环
continue;
}
// 初始渲染时只为 newFiber 添加了 index 属性,
// 其他事没干. lastPlacedIndex 被原封不动的返回了
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
// 为当前节点设置下一个兄弟节点
if (previousNewFiber === null) {
// 存储第一个子 Fiber 发生在第一次循环时
resultingFirstChild = newFiber;
} else {
// 为节点设置下一个兄弟 Fiber
previousNewFiber.sibling = newFiber;
}
// 在循环的过程中更新上一个创建的Fiber 对象
previousNewFiber = newFiber;
}
// 返回创建好的子 Fiber
// 其他 Fiber 都作为 sibling 存在
return resultingFirstChild;
}
// 下面的代码初始渲染不执行
// Add all children to a key map for quick lookups.
const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
// Keep scanning and use the map to restore deleted items as moves.
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = updateFromMap(
existingChildren,
returnFiber,
newIdx,
newChildren[newIdx],
expirationTime,
);
if (newFiber !== null) {
if (shouldTrackSideEffects) {
if (newFiber.alternate !== null) {
// The new fiber is a work in progress, but if there exists a
// current, that means that we reused the fiber. We need to delete
// it from the child list so that we don't add it to the deletion
// list.
existingChildren.delete(
newFiber.key === null ? newIdx : newFiber.key,
);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
}
// 初始渲染不执行
if (shouldTrackSideEffects) {
// Any existing children that weren't consumed above were deleted. We need
// to add them to the deletion list.
existingChildren.forEach((child) => deleteChild(returnFiber, child));
}
// 返回第一个子元素 Fiber 对象
return resultingFirstChild;
}
function reconcileChildrenIterator(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChildrenIterable: Iterable<*>,
expirationTime: ExpirationTime,
): Fiber | null {
// This is the same implementation as reconcileChildrenArray(),
// but using the iterator instead.
const iteratorFn = getIteratorFn(newChildrenIterable);
invariant(
typeof iteratorFn === 'function',
'An object is not an iterable. This error is likely caused by a bug in ' +
'React. Please file an issue.',
);
if (__DEV__) {
// We don't support rendering Generators because it's a mutation.
// See https://github.com/facebook/react/issues/12995
if (
typeof Symbol === 'function' &&
// $FlowFixMe Flow doesn't know about toStringTag
newChildrenIterable[Symbol.toStringTag] === 'Generator'
) {
if (!didWarnAboutGenerators) {
console.error(
'Using Generators as children is unsupported and will likely yield ' +
'unexpected results because enumerating a generator mutates it. ' +
'You may convert it to an array with `Array.from()` or the ' +
'`[...spread]` operator before rendering. Keep in mind ' +
'you might need to polyfill these features for older browsers.',
);
}
didWarnAboutGenerators = true;
}
// Warn about using Maps as children
if ((newChildrenIterable: any).entries === iteratorFn) {
if (!didWarnAboutMaps) {
console.error(
'Using Maps as children is unsupported and will likely yield ' +
'unexpected results. Convert it to a sequence/iterable of keyed ' +
'ReactElements instead.',
);
}
didWarnAboutMaps = true;
}
// First, validate keys.
// We'll get a different iterator later for the main pass.
const newChildren = iteratorFn.call(newChildrenIterable);
if (newChildren) {
let knownKeys = null;
let step = newChildren.next();
for (; !step.done; step = newChildren.next()) {
const child = step.value;
knownKeys = warnOnInvalidKey(child, knownKeys);
}
}
}
const newChildren = iteratorFn.call(newChildrenIterable);
invariant(newChildren != null, 'An iterable object provided no iterator.');
let resultingFirstChild: Fiber | null = null;
let previousNewFiber: Fiber | null = null;
let oldFiber = currentFirstChild;
let lastPlacedIndex = 0;
let newIdx = 0;
let nextOldFiber = null;
let step = newChildren.next();
for (
;
oldFiber !== null && !step.done;
newIdx++, step = newChildren.next()
) {
if (oldFiber.index > newIdx) {
nextOldFiber = oldFiber;
oldFiber = null;
} else {
nextOldFiber = oldFiber.sibling;
}
const newFiber = updateSlot(
returnFiber,
oldFiber,
step.value,
expirationTime,
);
if (newFiber === null) {
// TODO: This breaks on empty slots like null children. That's
// unfortunate because it triggers the slow path all the time. We need
// a better way to communicate whether this was a miss or null,
// boolean, undefined, etc.
if (oldFiber === null) {
oldFiber = nextOldFiber;
}
break;
}
if (shouldTrackSideEffects) {
if (oldFiber && newFiber.alternate === null) {
// We matched the slot, but we didn't reuse the existing fiber, so we
// need to delete the existing child.
deleteChild(returnFiber, oldFiber);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
// TODO: Move out of the loop. This only happens for the first run.
resultingFirstChild = newFiber;
} else {
// TODO: Defer siblings if we're not at the right index for this slot.
// I.e. if we had null values before, then we want to defer this
// for each null value. However, we also don't want to call updateSlot
// with the previous one.
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
oldFiber = nextOldFiber;
}
if (step.done) {
// We've reached the end of the new children. We can delete the rest.
deleteRemainingChildren(returnFiber, oldFiber);
return resultingFirstChild;
}
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 (; !step.done; newIdx++, step = newChildren.next()) {
const newFiber = createChild(returnFiber, step.value, expirationTime);
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;
}
// Add all children to a key map for quick lookups.
const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
// Keep scanning and use the map to restore deleted items as moves.
for (; !step.done; newIdx++, step = newChildren.next()) {
const newFiber = updateFromMap(
existingChildren,
returnFiber,
newIdx,
step.value,
expirationTime,
);
if (newFiber !== null) {
if (shouldTrackSideEffects) {
if (newFiber.alternate !== null) {
// The new fiber is a work in progress, but if there exists a
// current, that means that we reused the fiber. We need to delete
// it from the child list so that we don't add it to the deletion
// list.
existingChildren.delete(
newFiber.key === null ? newIdx : newFiber.key,
);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
}
if (shouldTrackSideEffects) {
// Any existing children that weren't consumed above were deleted. We need
// to add them to the deletion list.
existingChildren.forEach((child) => deleteChild(returnFiber, child));
}
return resultingFirstChild;
}
// 处理子元素是文本或者数值的情况
function reconcileSingleTextNode(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
textContent: string,
expirationTime: ExpirationTime,
): Fiber {
// 初始渲染不执行
if (currentFirstChild !== null && currentFirstChild.tag === HostText) {
// We already have an existing node so let's just update it and delete
// the rest.
deleteRemainingChildren(returnFiber, currentFirstChild.sibling);
const existing = useFiber(currentFirstChild, textContent);
existing.return = returnFiber;
return existing;
}
// 现有的第一个子节点不是文本节点,因此我们需要创建一个并删除现有的.
// 初始渲染不执行
deleteRemainingChildren(returnFiber, currentFirstChild);
// 根据文本创建 Fiber 对象
const created = createFiberFromText(
textContent,
returnFiber.mode,
expirationTime,
);
// 设置父 Fiber 对象
created.return = returnFiber;
// 返回创建好的 Fiber 对象
return created;
}
// 处理子元素是单个对象的情况
function reconcileSingleElement(
// 父 Fiber 对象
returnFiber: Fiber,
// 备份子 fiber
currentFirstChild: Fiber | null,
// 子 vdom 对象
element: ReactElement,
expirationTime: ExpirationTime,
): Fiber {
const key = element.key;
let child = currentFirstChild;
// 初始渲染 currentFirstChild 为 null
// false
while (child !== null) {
// TODO: If key === null and child.key === null, then this only applies to
// the first item in the list.
if (child.key === key) {
switch (child.tag) {
case Fragment: {
if (element.type === REACT_FRAGMENT_TYPE) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, element.props.children);
existing.return = returnFiber;
if (__DEV__) {
existing._debugSource = element._source;
existing._debugOwner = element._owner;
}
return existing;
}
break;
}
case Block:
if (enableBlocksAPI) {
if (
element.type.$$typeof === REACT_BLOCK_TYPE &&
element.type.render === child.type.render
) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, element.props);
existing.type = element.type;
existing.return = returnFiber;
if (__DEV__) {
existing._debugSource = element._source;
existing._debugOwner = element._owner;
}
return existing;
}
}
// We intentionally fallthrough here if enableBlocksAPI is not on.
// eslint-disable-next-lined no-fallthrough
default: {
if (
child.elementType === element.type ||
// Keep this check inline so it only runs on the false path:
(__DEV__
? isCompatibleFamilyForHotReloading(child, element)
: false)
) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, element.props);
existing.ref = coerceRef(returnFiber, child, element);
existing.return = returnFiber;
if (__DEV__) {
existing._debugSource = element._source;
existing._debugOwner = element._owner;
}
return existing;
}
break;
}
}
// Didn't match.
deleteRemainingChildren(returnFiber, child);
break;
} else {
deleteChild(returnFiber, child);
}
child = child.sibling;
}
// 查看子 vdom 对象是否表示 fragment
// false
if (element.type === REACT_FRAGMENT_TYPE) {
const created = createFiberFromFragment(
element.props.children,
returnFiber.mode,
expirationTime,
element.key,
);
created.return = returnFiber;
return created;
} else {
// 根据 React Element 创建 Fiber 对象
// 返回创建好的 Fiber 对象
const created = createFiberFromElement(
element,
// 用来表示当前组件下的所有子组件要用处于何种渲染模式
// 文件位置: ./ReactTypeOfMode.js
// 0 同步渲染模式
// 100 异步渲染模式
returnFiber.mode,
expirationTime,
);
// 添加 ref 属性 { current: DOM }
created.ref = coerceRef(returnFiber, currentFirstChild, element);
// 添加父级 Fiber 对象
created.return = returnFiber;
// 返回创建好的子 Fiber
return created;
}
}
function reconcileSinglePortal(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
portal: ReactPortal,
expirationTime: ExpirationTime,
): Fiber {
const key = portal.key;
let child = currentFirstChild;
while (child !== null) {
// TODO: If key === null and child.key === null, then this only applies to
// the first item in the list.
if (child.key === key) {
if (
child.tag === HostPortal &&
child.stateNode.containerInfo === portal.containerInfo &&
child.stateNode.implementation === portal.implementation
) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, portal.children || []);
existing.return = returnFiber;
return existing;
} else {
deleteRemainingChildren(returnFiber, child);
break;
}
} else {
deleteChild(returnFiber, child);
}
child = child.sibling;
}
const created = createFiberFromPortal(
portal,
returnFiber.mode,
expirationTime,
);
created.return = returnFiber;
return created;
}
// This API will tag the children with the side-effect of the reconciliation
// itself. They will be added to the side-effect list as we pass through the
// children and the parent.
function reconcileChildFibers(
// 父 Fiber 对象
returnFiber: Fiber,
// 旧的第一个子 Fiber 初始渲染 null
currentFirstChild: Fiber | null,
// 新的子 vdom 对象
newChild: any,
// 初始渲染 整型最大值 代表同步任务
expirationTime: ExpirationTime,
): Fiber | null {
// 这是入口方法, 根据 newChild 类型进行对应处理
// 判断新的子 vdom 是否为占位组件 比如 <></>
// false
const isUnkeyedTopLevelFragment =
typeof newChild === 'object' &&
newChild !== null &&
newChild.type === REACT_FRAGMENT_TYPE &&
newChild.key === null;
// 如果 newChild 为占位符, 使用 占位符组件的子元素作为 newChild
if (isUnkeyedTopLevelFragment) {
newChild = newChild.props.children;
}
// 检测 newChild 是否为对象类型
const isObject = typeof newChild === 'object' && newChild !== null;
// newChild 是单个对象的情况
if (isObject) {
// 匹配子元素的类型
switch (newChild.$$typeof) {
// 子元素为 ReactElement
case REACT_ELEMENT_TYPE:
// 为 Fiber 对象设置 effectTag 属性
// 返回创建好的子 Fiber
return placeSingleChild(
// 处理单个 React Element 的情况
// 内部会调用其他方法创建对应的 Fiber 对象
reconcileSingleElement(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
),
);
case REACT_PORTAL_TYPE:
return placeSingleChild(
reconcileSinglePortal(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
),
);
}
}
// 处理 children 为文本和数值的情况 return "App works"
if (typeof newChild === 'string' || typeof newChild === 'number') {
return placeSingleChild(
reconcileSingleTextNode(
returnFiber,
currentFirstChild,
// 如果 newChild 是数值, 转换为字符串
'' + newChild,
expirationTime,
),
);
}
// children 是数组的情况
if (isArray(newChild)) {
// 返回创建好的子 Fiber
return reconcileChildrenArray(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
);
}
if (getIteratorFn(newChild)) {
return reconcileChildrenIterator(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
);
}
if (isObject) {
throwOnInvalidObjectType(returnFiber, newChild);
}
if (__DEV__) {
if (typeof newChild === 'function') {
warnOnFunctionType();
}
}
if (typeof newChild === 'undefined' && !isUnkeyedTopLevelFragment) {
// If the new child is undefined, and the return fiber is a composite
// component, throw an error. If Fiber return types are disabled,
// we already threw above.
switch (returnFiber.tag) {
case ClassComponent: {
if (__DEV__) {
const instance = returnFiber.stateNode;
if (instance.render._isMockFunction) {
// We allow auto-mocks to proceed as if they're returning null.
break;
}
}
}
// Intentionally fall through to the next case, which handles both
// functions and classes
// eslint-disable-next-lined no-fallthrough
case FunctionComponent: {
const Component = returnFiber.type;
invariant(
false,
'%s(...): Nothing was returned from render. This usually means a ' +
'return statement is missing. Or, to render nothing, ' +
'return null.',
Component.displayName || Component.name || 'Component',
);
}
}
}
// Remaining cases are all treated as empty.
return deleteRemainingChildren(returnFiber, currentFirstChild);
}
return reconcileChildFibers;
}
// 根据 React Element 创建 Fiber 对象
export function createFiberFromElement(
element: ReactElement,
// 父级Fiber mode 子级需要继承
mode: TypeOfMode,
expirationTime: ExpirationTime,
): Fiber {
let owner = null;
if (__DEV__) {
owner = element._owner;
}
const type = element.type;
const key = element.key;
const pendingProps = element.props;
const fiber = createFiberFromTypeAndProps(
type,
key,
pendingProps,
owner,
mode,
expirationTime,
);
if (__DEV__) {
fiber._debugSource = element._source;
fiber._debugOwner = element._owner;
}
return fiber;
}
export function createFiberFromTypeAndProps(
type: any, // React$ElementType
key: null | string,
pendingProps: any,
owner: null | Fiber,
mode: TypeOfMode,
expirationTime: ExpirationTime,
): Fiber {
let fiber;
let fiberTag = IndeterminateComponent;
// The resolved type is set if we know what the final type will be. I.e. it's not lazy.
let resolvedType = type;
if (typeof type === 'function') {
if (shouldConstruct(type)) {
fiberTag = ClassComponent;
if (__DEV__) {
resolvedType = resolveClassForHotReloading(resolvedType);
}
} else {
if (__DEV__) {
resolvedType = resolveFunctionForHotReloading(resolvedType);
}
}
} else if (typeof type === 'string') {
fiberTag = HostComponent;
} else {
getTag: switch (type) {
case REACT_FRAGMENT_TYPE:
return createFiberFromFragment(
pendingProps.children,
mode,
expirationTime,
key,
);
case REACT_CONCURRENT_MODE_TYPE:
fiberTag = Mode;
mode |= ConcurrentMode | BlockingMode | StrictMode;
break;
case REACT_STRICT_MODE_TYPE:
fiberTag = Mode;
mode |= StrictMode;
break;
case REACT_PROFILER_TYPE:
return createFiberFromProfiler(pendingProps, mode, expirationTime, key);
case REACT_SUSPENSE_TYPE:
return createFiberFromSuspense(pendingProps, mode, expirationTime, key);
case REACT_SUSPENSE_LIST_TYPE:
return createFiberFromSuspenseList(
pendingProps,
mode,
expirationTime,
key,
);
default: {
if (typeof type === 'object' && type !== null) {
switch (type.$$typeof) {
case REACT_PROVIDER_TYPE:
fiberTag = ContextProvider;
break getTag;
case REACT_CONTEXT_TYPE:
// This is a consumer
fiberTag = ContextConsumer;
break getTag;
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;
case REACT_BLOCK_TYPE:
fiberTag = Block;
break getTag;
case REACT_FUNDAMENTAL_TYPE:
if (enableFundamentalAPI) {
return createFiberFromFundamental(
type,
pendingProps,
mode,
expirationTime,
key,
);
}
break;
case REACT_SCOPE_TYPE:
if (enableScopeAPI) {
return createFiberFromScope(
type,
pendingProps,
mode,
expirationTime,
key,
);
}
}
}
let info = '';
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.';
}
const ownerName = owner ? getComponentName(owner.type) : null;
if (ownerName) {
info += '\n\nCheck the render method of `' + ownerName + '`.';
}
}
invariant(
false,
'Element type is invalid: expected a string (for built-in ' +
'components) or a class/function (for composite components) ' +
'but got: %s.%s',
type == null ? type : typeof type,
info,
);
}
}
}
fiber = createFiber(fiberTag, pendingProps, key, mode);
fiber.elementType = type;
fiber.type = resolvedType;
fiber.expirationTime = expirationTime;
return fiber;
}
一、初次渲染流程图
二、下载源码到本地
三、CreateElement 具体做了哪些操作
1. 入口文件
src/react/packages/react/src/React.js
import {
createElement as createElementProd,
createFactory as createFactoryProd,
cloneElement as cloneElementProd,
isValidElement,
jsx as jsxProd,
} from './ReactElement';
2. 分离 Props 和 特殊属性, 并挂载 props.children
src/react/packages/react/src/ReactElement.js
/**
* 创建 React Element
* type 元素类型
* config 配置属性
* children 子元素
* 1. 分离 props 属性和特殊属性
* 2. 将子元素挂载到 props.children 中
* 3. 为 props 属性赋默认值 (defaultProps)
* 4. 创建并返回 ReactElement
*/
export function createElement(type, config, children) {
console.log('test')
/**
* propName -> 属性名称
* 用于后面的 for 循环
*/
let propName;
/**
* 存储 React Element 中的普通元素属性 即不包含 key ref self source
*/
const props = {};
/**
* 待提取属性
* React 内部为了实现某些功能而存在的属性
*/
let key = null;
let ref = null;
let self = null;
let source = null;
// 如果 config 不为 null
if (config != null) {
// 如果 config 对象中有合法的 ref 属性
if (hasValidRef(config)) {
// 将 config.ref 属性提取到 ref 变量中
ref = config.ref;
// 在开发环境中
if (__DEV__) {
// 如果 ref 属性的值被设置成了字符串形式就报一个提示
// 说明此用法在将来的版本中会被删除
warnIfStringRefCannotBeAutoConverted(config);
}
}
// 如果在 config 对象中拥有合法的 key 属性
if (hasValidKey(config)) {
// 将 config.key 属性中的值提取到 key 变量中
key = '' + config.key;
}
self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;
// 遍历 config 对象
for (propName in config) {
// 如果当前遍历到的属性是对象自身属性
// 并且在 RESERVED_PROPS 对象中不存在该属性
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
// 将满足条件的属性添加到 props 对象中 (普通属性)
props[propName] = config[propName];
}
}
}
/**
* 将第三个及之后的参数挂载到 props.children 属性中
* 如果子元素是多个 props.children 是数组
* 如果子元素是一个 props.children 是对象
*/
// 由于从第三个参数开始及以后都表示子元素
// 所以减去前两个参数的结果就是子元素的数量
const childrenLength = arguments.length - 2;
// 如果子元素的数量是 1
if (childrenLength === 1) {
// 直接将子元素挂载到到 props.children 属性上
// 此时 children 是对象类型
props.children = children;
// 如果子元素的数量大于 1
} else if (childrenLength > 1) {
// 创建数组, 数组中元素的数量等于子元素的数量
const childArray = Array(childrenLength);
// 开启循环 循环次匹配子元素的数量
for (let i = 0; i < childrenLength; i++) {
// 将子元素添加到 childArray 数组中
// i + 2 的原因是实参集合的前两个参数不是子元素
childArray[i] = arguments[i + 2];
}
// 如果是开发环境
if (__DEV__) {
// 如果 Object 对象中存在 freeze 方法
if (Object.freeze) {
// 调用 freeze 方法 冻结 childArray 数组
// 防止 React 核心对象被修改 冻结对象提高性能
Object.freeze(childArray);
}
}
// 将子元素数组挂载到 props.children 属性中
props.children = childArray;
}
/**
* 如果当前处理是组件
* 看组件身上是否有 defaultProps 属性
* 这个属性中存储的是 props 对象中属性的默认值
* 遍历 defaultProps 对象 查看对应的 props 属性的值是否为 undefined
* 如果为undefined 就将默认值赋值给对应的 props 属性值
*/
// 将 type 属性值视为函数 查看其中是否具有 defaultProps 属性
if (type && type.defaultProps) {
// 将 type 函数下的 defaultProps 属性赋值给 defaultProps 变量
const defaultProps = type.defaultProps;
// 遍历 defaultProps 对象中的属性 将属性名称赋值给 propName 变量
for (propName in defaultProps) {
// 如果 props 对象中的该属性的值为 undefined
if (props[propName] === undefined) {
// 将 defaultProps 对象中的对应属性的值赋值给 props 对象中的对应属性的值
props[propName] = defaultProps[propName];
}
}
}
/**
* 在开发环境中 React 会检测开发者是否在组件内部
* 通过 props 对象获取 key 属性或者 ref 属性
* 如果开发者调用了 在控制台中报错误提示
*/
// 如果处于开发环境
if (__DEV__) {
// 元素具有 key 属性或者 ref 属性
if (key || ref) {
// 看一下 type 属性中存储的是否是函数 如果是函数就表示当前元素是组件
// 如果元素不是组件 就直接返回元素类型字符串
// displayName 用于在报错过程中显示是哪一个组件报错了
// 如果开发者显式定义了 displayName 属性 就显示开发者定义的
// 否者就显示组件名称 如果组件也没有名称 就显示 'Unknown'
const displayName =
typeof type === 'function'
? type.displayName || type.name || 'Unknown'
: type;
// 如果 key 属性存在
if (key) {
// 为 props 对象添加key 属性
// 并指定当通过 props 对象获取 key 属性时报错
defineKeyPropWarningGetter(props, displayName);
}
// 如果 ref 属性存在
if (ref) {
// 为 props 对象添加 ref 属性
// 并指定当通过 props 对象获取 ref 属性时报错
defineRefPropWarningGetter(props, displayName);
}
}
}
// 返回 ReactElement
return ReactElement(
type,
key,
ref,
self,
source,
// 在 Virtual DOM 中用于识别自定义组件
ReactCurrentOwner.current,
props,
);
}
三、Render 为 入口
1. 导出 render
packages/react-dom/src/client/ReactDOM.js
import {
findDOMNode,
render,
hydrate,
unstable_renderSubtreeIntoContainer,
unmountComponentAtNode,
} from './ReactDOMLegacy';
export {
createPortal,
batchedUpdates as unstable_batchedUpdates,
flushSync,
Internals as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
ReactVersion as version,
// Disabled behind disableLegacyReactDOMAPIs
findDOMNode,
hydrate,
render, // 此处导出
unmountComponentAtNode,
// exposeConcurrentModeAPIs
createRoot,
flushControlled as unstable_flushControlled,
scheduleHydration as unstable_scheduleHydration,
// Disabled behind disableUnstableRenderSubtreeIntoContainer
renderSubtreeIntoContainer as unstable_renderSubtreeIntoContainer,
// enableCreateEventHandleAPI
createEventHandle as unstable_createEventHandle,
// TODO: Remove this once callers migrate to alternatives.
// This should only be used by React internals.
runWithPriority as unstable_runWithPriority,
};
2. 当初始化渲染时,删除 container 内其他额外的元素,并在 root 上挂载 _internalRoot
packages/react-dom/src/client/ReactDOMLegacy.js
/**
* 渲染入口
* element 要进行渲染的 ReactElement
* container 渲染容器
* callback 渲染完成后执行的回调函数
*/
export function render(
element: React$Element<any>,
container: Container,
callback: ?Function,
) {
// 检测 container 是否是符合要求的渲染容器
// 即检测 container 是否是真实的DOM对象
// 如果不符合要求就报错
invariant(
isValidContainer(container),
'Target container is not a DOM element.',
);
// 在开发环境下
if (__DEV__) {
// 检测 container 是否已经传递给 ReactDOM.createRoot() 方法
// 如果已经传递, 则 container 不能再传递给 render 方法
// 防止 render 方法和 createRoot 方法重复调用
const isModernRoot =
isContainerMarkedAsRoot(container) &&
container._reactRootContainer === undefined;
// 如果 container 已经传递给过 createRoot 方法
if (isModernRoot) {
// 在控制台报错
console.error(
'You are calling ReactDOM.render() on a container that was previously ' +
'passed to ReactDOM.createRoot(). This is not supported. ' +
'Did you mean to call root.render(element)?',
);
}
}
return legacyRenderSubtreeIntoContainer(
// 父组件 初始渲染没有父组件 传递 null 占位
null,
element,
container,
// 是否为服务器端渲染 false 不是服务器端渲染 true 是服务器端渲染
false,
callback,
);
}
/**
* 判断是否为服务器端渲染 如果不是服务器端渲染
* 清空 container 容器中的节点
*/
function legacyCreateRootFromDOMContainer(
container: Container,
forceHydrate: boolean,
): RootType {
// container => <div id="root"></div>
// 检测是否为服务器端渲染
const shouldHydrate =
forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
// 如果不是服务器端渲染
if (!shouldHydrate) {
let warned = false;
let rootSibling;
// 开启循环 删除 container 容器中的节点
while ((rootSibling = container.lastChild)) {
// 在开发环境中
if (__DEV__) {
/**
* 判断子节点是否为元素节点, 并且元素节点不能有 data-reactroot 属性, 否则报错
* 通常在服务器端渲染时会遇到这个问题
*
* <div id="app">
* <%- markup %>
* </div>
* ↑ 报错
*
* <div id="app"><%- markup %></div>
* ↑ 不报错 删除了所有空白区域
*/
if (
!warned &&
rootSibling.nodeType === ELEMENT_NODE &&
// 判断是否是服务端渲染的标志
(rootSibling: any).hasAttribute(ROOT_ATTRIBUTE_NAME)
) {
warned = true;
console.error(
'render(): Target node has markup rendered by React, but there ' +
'are unrelated nodes as well. This is most commonly caused by ' +
'white-space inserted around server-rendered markup.',
);
}
}
// 删除 container 容器中的节点
container.removeChild(rootSibling);
/**
* 为什么要清除 container 中的元素 ?
* 有时需要在 container 中放置一些占位图或者 loading 图以提高首屏加载用户体验,
* 就无可避免的要向 container 中加入 html 标记.
* 在将 ReactElement 渲染到 container 之前, 必然要先清空 container
* 因为占位图和 ReactElement 不能同时显示
*
* 在加入占位代码时, 最好只有一个父级元素, 可以减少内部代码的循环次数以提高性能
* <div>
* <p>placement<p>
* <p>placement<p>
* <p>placement<p>
* </div>
*/
}
}
// 在开发环境下
if (__DEV__) {
// 如果是服务器端渲染 控制台提示错误
if (shouldHydrate && !forceHydrate && !warnedAboutHydrateAPI) {
warnedAboutHydrateAPI = true;
// 在客户端二次渲染时不要使用 render 方法, 要使用 hydrate 方法替代
console.warn(
'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
'will stop working in React v17. Replace the ReactDOM.render() call ' +
'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
);
}
}
return createLegacyRoot(
container,
shouldHydrate
? {
hydrate: true,
}
: undefined,
);
}
/**
* 通过实例化 ReactDOMBlockingRoot 类创建 LegacyRoot
*/
export function createLegacyRoot(
container: Container,
options?: RootOptions,
): RootType {
// container => <div id="root"></div>
// LegacyRoot 常量, 值为 0,
// 通过 render 方法创建的 container 就是 LegacyRoot
return new ReactDOMBlockingRoot(container, LegacyRoot, options);
}
/**
* 创建 ReactDOMBlockingRoot 的类
* 通过它可以创建 LegacyRoot 的 Fiber 数据结构
*/
function ReactDOMBlockingRoot(
container: Container,
tag: RootTag,
options: void | RootOptions,
) {
// tag => 0 => legacyRoot
// container => <div id="root"></div>
// container._reactRootContainer = {_internalRoot: {}}
this._internalRoot = createRootImpl(container, tag, options);
}
3. 创建 Container
src/react/packages/react-reconciler/src/ReactFiberReconciler.js
export function createContainer(
containerInfo: Container,
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
): OpaqueRoot {
// containerInfo => <div id="root"></div>
// tag: 0
// hydrate: false
// hydrationCallbacks: null
return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
}
4. 创建 FirberRoot 对象
// 创建根节点对应的 fiber 对象
export function createFiberRoot(
containerInfo: any,
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
): FiberRoot {
// 创建 FiberRoot
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
// false
if (enableSuspenseCallback) {
root.hydrationCallbacks = hydrationCallbacks;
}
// 创建根节点对应的 rootFiber
const uninitializedFiber = createHostRootFiber(tag);
// 为 fiberRoot 添加 current 属性 值为 rootFiber
root.current = uninitializedFiber;
// 为 rootFiber 添加 stateNode 属性 值为 fiberRoot
uninitializedFiber.stateNode = root;
// 为 fiber 对象添加 updateQueue 属性, 初始化 updateQueue 对象
// updateQueue 用于存放 Update 对象
// Update 对象用于记录组件状态的改变
initializeUpdateQueue(uninitializedFiber);
// 返回 root
return root;
}
// FiberRoot 对应的属性
function FiberRootNode(containerInfo, tag, hydrate) {
this.tag = tag;
this.current = null;
this.containerInfo = containerInfo;
this.pendingChildren = null;
this.pingCache = null;
this.finishedExpirationTime = NoWork;
this.finishedWork = null;
this.timeoutHandle = noTimeout;
this.context = null;
this.pendingContext = null;
this.hydrate = hydrate;
this.callbackNode = null;
this.callbackPriority = NoPriority;
this.firstPendingTime = NoWork;
this.firstSuspendedTime = NoWork;
this.lastSuspendedTime = NoWork;
this.nextKnownPendingLevel = NoWork;
this.lastPingedTime = NoWork;
this.lastExpiredTime = NoWork;
if (enableSchedulerTracing) {
this.interactionThreadID = unstable_getThreadID();
this.memoizedInteractions = new Set();
this.pendingInteractionMap = new Map();
}
if (enableSuspenseCallback) {
this.hydrationCallbacks = null;
}
}

5. 创建 RootFirber 对象
src/react/packages/react-reconciler/src/ReactFiber.js
export function createHostRootFiber(tag: RootTag): Fiber {
// 根据 tag 值设置 mode
let mode;
if (tag === ConcurrentRoot) {
mode = ConcurrentMode | BlockingMode | StrictMode;
} else if (tag === BlockingRoot) {
mode = BlockingMode | StrictMode;
} else {
mode = NoMode;
}
if (enableProfilerTimer && isDevToolsPresent) {
// Always collect profile timings when DevTools are present.
// This enables DevTools to start capturing timing at any point–
// Without some nodes in the tree having empty base times.
mode |= ProfileMode;
}
return createFiber(HostRoot, null, null, mode);
}
// This is a constructor function, rather than a POJO constructor, still
// please ensure we do the following:
// 1) Nobody should add any instance methods on this. Instance methods can be
// more difficult to predict when they get optimized and they are almost
// never inlined properly in static compilers.
// 2) Nobody should rely on `instanceof Fiber` for type testing. We should
// always know when it is a fiber.
// 3) We might want to experiment with using numeric keys since they are easier
// to optimize in a non-JIT environment.
// 4) We can easily go from a constructor to a createFiber object literal if that
// is faster.
// 5) It should be easy to port this to a C struct and keep a C implementation
// compatible.
const createFiber = function (
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
): Fiber {
// $FlowFixMe: the shapes are exact here but Flow doesn't like constructors
return new FiberNode(tag, pendingProps, key, mode);
};
// firber 的属性结构
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.effectTag = NoEffect;
this.nextEffect = null;
this.firstEffect = null;
this.lastEffect = null;
this.expirationTime = NoWork;
this.childExpirationTime = NoWork;
this.alternate = null;
if (enableProfilerTimer) {
// Note: The following is done to avoid a v8 performance cliff.
//
// Initializing the fields below to smis and later updating them with
// double values will cause Fibers to end up having separate shapes.
// This behavior/bug has something to do with Object.preventExtension().
// Fortunately this only impacts DEV builds.
// Unfortunately it makes React unusably slow for some applications.
// To work around this, initialize the fields below with doubles.
//
// Learn more about this here:
// https://github.com/facebook/react/issues/14365
// https://bugs.chromium.org/p/v8/issues/detail?id=8538
this.actualDuration = Number.NaN;
this.actualStartTime = Number.NaN;
this.selfBaseDuration = Number.NaN;
this.treeBaseDuration = Number.NaN;
// It's okay to replace the initial doubles with smis after initialization.
// This won't trigger the performance cliff mentioned above,
// and it simplifies other profiler code (including DevTools).
this.actualDuration = 0;
this.actualStartTime = -1;
this.selfBaseDuration = 0;
this.treeBaseDuration = 0;
}
// This is normally DEV-only except www when it adds listeners.
// TODO: remove the User Timing integration in favor of Root Events.
if (enableUserTimingAPI) {
this._debugID = debugCounter++;
this._debugIsCurrentlyTiming = false;
}
if (__DEV__) {
this._debugSource = null;
this._debugOwner = null;
this._debugNeedsRemount = false;
this._debugHookTypes = null;
if (!hasBadMapPolyfill && typeof Object.preventExtensions === 'function') {
Object.preventExtensions(this);
}
}
}

6. 在 FirberRoot 中挂载 RootFirber, 在 RootFirber 挂载 FirberRoot
// 创建根节点对应的 fiber 对象
export function createFiberRoot(
containerInfo: any,
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
): FiberRoot {
// 创建 FiberRoot
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
// false
if (enableSuspenseCallback) {
root.hydrationCallbacks = hydrationCallbacks;
}
// 创建根节点对应的 rootFiber
const uninitializedFiber = createHostRootFiber(tag);
// 为 fiberRoot 添加 current 属性 值为 rootFiber
root.current = uninitializedFiber;
// 为 rootFiber 添加 stateNode 属性 值为 fiberRoot
uninitializedFiber.stateNode = root;
// 为 fiber 对象添加 updateQueue 属性, 初始化 updateQueue 对象
// updateQueue 用于存放 Update 对象
// Update 对象用于记录组件状态的改变
initializeUpdateQueue(uninitializedFiber);
// 返回 root
return root;
}
7. 为 rootfiber 对象添加 updateQueue 属性,及记录组件的状态
// rootFirber 中挂载 updateQueue 以下属性,用于记录更新的状态
export function initializeUpdateQueue<State>(fiber: Fiber): void {
const queue: UpdateQueue<State> = {
baseState: fiber.memoizedState,
baseQueue: null,
shared: {
pending: null,
},
effects: null,
};
fiber.updateQueue = queue;
}
8. 根元素 Root 挂载的值,及 FirberRoot = Root._internalRoot
/**
* 将子树渲染到容器中 (初始化 Fiber 数据结构: 创建 fiberRoot 及 rootFiber)
* parentComponent: 父组件, 初始渲染传入了 null
* children: render 方法中的第一个参数, 要渲染的 ReactElement
* container: 渲染容器
* forceHydrate: true 为服务端渲染, false 为客户端渲染
* callback: 组件渲染完成后需要执行的回调函数
**/
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
container: Container,
forceHydrate: boolean,
callback: ?Function,
) {
if (__DEV__) {
topLevelUpdateWarnings(container);
warnOnInvalidCallback(callback === undefined ? null : callback, 'render');
}
/**
* 检测 container 是否已经是初始化过的渲染容器
* react 在初始渲染时会为最外层容器添加 _reactRootContainer 属性
* react 会根据此属性进行不同的渲染方式
* root 不存在 表示初始渲染
* root 存在 表示更新
*/
// 获取 container 容器对象下是否有 _reactRootContainer 属性
let root: RootType = (container._reactRootContainer: any);
// 即将存储根 Fiber 对象
let fiberRoot;
if (!root) {
// 初始渲染
// 初始化根 Fiber 数据结构
// 为 container 容器添加 _reactRootContainer 属性
// 在 _reactRootContainer 对象中有一个属性叫做 _internalRoot
// _internalRoot 属性值即为 FiberRoot 表示根节点 Fiber 数据结构
// legacyCreateRootFromDOMContainer
// createLegacyRoot
// new ReactDOMBlockingRoot -> this._internalRoot
// createRootImpl
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
// 获取 Fiber Root 对象
fiberRoot = root._internalRoot;
/**
* 改变 callback 函数中的 this 指向
* 使其指向 render 方法第一个参数的真实 DOM 对象
*/
// 如果 callback 参数是函数类型
if (typeof callback === 'function') {
// 使用 originalCallback 存储 callback 函数
const originalCallback = callback;
// 为 callback 参数重新赋值
callback = function () {
// 获取 render 方法第一个参数的真实 DOM 对象
// 实际上就是 id="root" 的 div 的子元素
// rootFiber.child.stateNode
// rootFiber 就是 id="root" 的 div
const instance = getPublicRootInstance(fiberRoot);
// 调用原始 callback 函数并改变函数内部 this 指向
originalCallback.call(instance);
};
}
// 初始化渲染不执行批量更新
// 因为批量更新是异步的是可以被打断的, 但是初始化渲染应该尽快完成不能被打断
// 所以不执行批量更新
unbatchedUpdates(() => {
updateContainer(children, fiberRoot, parentComponent, callback);
});
} else {
// 非初始化渲染 即更新
fiberRoot = root._internalRoot;
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function () {
const instance = getPublicRootInstance(fiberRoot);
originalCallback.call(instance);
};
}
// Update
updateContainer(children, fiberRoot, parentComponent, callback);
}
// 返回 render 方法第一个参数的真实 DOM 对象作为 render 方法的返回值
// 就是说渲染谁 返回谁的真实 DOM 对象
return getPublicRootInstance(fiberRoot);
}
总结:以上已经完成了在 Root 根对象上初始化所需要挂载的属性,包括
9. 初始化任务队列
import {
updateContainer,
} from 'react-reconciler/inline.dom';
/**
* 计算任务的过期时间
* 再根据任务过期时间创建 Update 任务
* 通过任务的过期时间还可以计算出任务的优先级
*/
export function updateContainer(
// element 要渲染的 ReactElement
element: ReactNodeList,
// container Fiber Root 对象
container: OpaqueRoot,
// parentComponent 父组件 初始渲染为 null
parentComponent: ?React$Component<any, any>,
// ReactElement 渲染完成执行的回调函数
callback: ?Function,
): ExpirationTime {
if (__DEV__) {
onScheduleRoot(container, element);
}
// container 获取 rootFiber
const current = container.current;
// 获取当前距离 react 应用初始化的时间 1073741805
const currentTime = requestCurrentTimeForUpdate();
if (__DEV__) {
// $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
if ('undefined' !== typeof jest) {
warnIfUnmockedScheduler(current);
warnIfNotScopedWithMatchingAct(current);
}
}
// 异步加载设置
const suspenseConfig = requestCurrentSuspenseConfig();
// 计算过期时间
// 为防止任务因为优先级的原因一直被打断而未能执行
// react 会设置一个过期时间, 当时间到了过期时间的时候
// 如果任务还未执行的话, react 将会强制执行该任务
// 初始化渲染时, 任务同步执行不涉及被打断的问题
// 过期时间被设置成了 1073741823, 这个数值表示当前任务为同步任务
const expirationTime = computeExpirationForFiber(
currentTime,
current,
suspenseConfig,
);
// 设置FiberRoot.context, 首次执行返回一个emptyContext, 是一个 {}
const context = getContextForSubtree(parentComponent);
// 初始渲染时 Fiber Root 对象中的 context 属性值为 null
// 所以会进入到 if 中
if (container.context === null) {
// 初始渲染时将 context 属性值设置为 {}
container.context = context;
} else {
container.pendingContext = context;
}
if (__DEV__) {
if (
ReactCurrentFiberIsRendering &&
ReactCurrentFiberCurrent !== null &&
!didWarnAboutNestedUpdates
) {
didWarnAboutNestedUpdates = true;
console.error(
'Render methods should be a pure function of props and state; ' +
'triggering nested component updates from render is not allowed. ' +
'If necessary, trigger nested updates in componentDidUpdate.\n\n' +
'Check the render method of %s.',
getComponentName(ReactCurrentFiberCurrent.type) || 'Unknown',
);
}
}
// 创建一个待执行任务
const update = createUpdate(expirationTime, suspenseConfig);
// 将要更新的内容挂载到更新对象中的 payload 中
// 将要更新的组件存储在 payload 对象中, 方便后期获取
update.payload = {element};
// 判断 callback 是否存在
callback = callback === undefined ? null : callback;
// 如果 callback 存在
if (callback !== null) {
if (__DEV__) {
if (typeof callback !== 'function') {
console.error(
'render(...): Expected the last optional `callback` argument to be a ' +
'function. Instead received: %s.',
callback,
);
}
}
// 将 callback 挂载到 update 对象中
// 其实就是一层层传递 方便 ReactElement 元素渲染完成调用
// 回调函数执行完成后会被清除 可以在代码的后面加上 return 进行验证
update.callback = callback;
}
// 将 update 对象加入到当前 Fiber 的更新队列当中 (updateQueue)
// 待执行的任务都会被存储在 fiber.updateQueue.shared.pending 中
enqueueUpdate(current, update);
// 调度和更新 current 对象
scheduleWork(current, expirationTime);
// 返回过期时间
return expirationTime;
}
// 创建一个待执行任务
export function createUpdate(
expirationTime: ExpirationTime,
suspenseConfig: null | SuspenseConfig,
): Update<*> {
let update: Update<*> = {
expirationTime,
suspenseConfig,
tag: UpdateState,
payload: null,
callback: null,
next: (null: any),
};
update.next = update;
if (__DEV__) {
update.priority = getCurrentPriorityLevel();
}
return update;
}
// 将任务(Update)存放于任务队列(updateQueue)中
// 创建单向链表结构存放 update, next 用来串联 update
export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
// 获取当前 Fiber 的 更新队列
const updateQueue = fiber.updateQueue;
// 如果更新队列不存在 就返回 null
if (updateQueue === null) {
// 仅发生在 fiber 已经被卸载
return;
}
// 获取待执行的 Update 任务
// 初始渲染时没有待执行的任务
const sharedQueue = updateQueue.shared;
const pending = sharedQueue.pending;
// 如果没有待执行的 Update 任务
if (pending === null) {
// 这是第一次更新, 创建一个循环列表.
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
// 将 Update 任务存储在 pending 属性中
sharedQueue.pending = update;
if (__DEV__) {
if (
currentlyProcessingQueue === sharedQueue &&
!didWarnUpdateInsideUpdate
) {
console.error(
'An update (setState, replaceState, or forceUpdate) was scheduled ' +
'from inside an update function. Update functions should be pure, ' +
'with zero side-effects. Consider using componentDidUpdate or a ' +
'callback.',
);
didWarnUpdateInsideUpdate = true;
}
}
}
// 调度和更新 current 对象
export const scheduleWork = scheduleUpdateOnFiber;
/**
* 判断任务是否为同步 调用同步任务入口
*/
export function scheduleUpdateOnFiber(
fiber: Fiber,
expirationTime: ExpirationTime,
) {
/**
* fiber: 初始化渲染时为 rootFiber, 即 <div id="root"></div> 对应的 Fiber 对象
* expirationTime: 任务过期时间 => 1073741823
*/
/**
* 判断是否是无限循环的 update 如果是就报错
* 在 componentWillUpdate 或者 componentDidUpdate 生命周期函数中重复调用
* setState 方法时, 可能会发生这种情况, React 限制了嵌套更新的数量以防止无限循环
* 限制的嵌套更新数量为 50, 可通过 NESTED_UPDATE_LIMIT 全局变量获取
*/
checkForNestedUpdates();
// 开发环境下执行的代码 忽略
warnAboutRenderPhaseUpdatesInDEV(fiber);
// 遍历更新子节点的过期时间 返回 FiberRoot
const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);
if (root === null) {
// 开发环境下执行 忽略
warnAboutUpdateOnUnmountedFiberInDEV(fiber);
return;
}
// 判断是否有高优先级任务打断当前正在执行的任务
// 内部判断条件不成立 内部代码没有得到执行
checkForInterruption(fiber, expirationTime);
// 报告调度更新, 测试环境执行, 忽略
recordScheduleUpdate();
// 获取当前调度任务的优先级 数值类型 从90开始 数值越大 优先级越高
// 97 普通优先级任务
const priorityLevel = getCurrentPriorityLevel();
// 判断任务是否是同步任务 Sync的值为: 1073741823
if (expirationTime === Sync) {
if (
// 检查是否处于非批量更新模式
(executionContext & LegacyUnbatchedContext) !== NoContext &&
// 检查是否没有处于正在进行渲染的任务
(executionContext & (RenderContext | CommitContext)) === NoContext
) {
// 在根上注册待处理的交互, 以避免丢失跟踪的交互数据
// 初始渲染时内部条件判断不成立, 内部代码没有得到执行
schedulePendingInteractions(root, expirationTime);
// 同步任务入口点
performSyncWorkOnRoot(root);
} else {
ensureRootIsScheduled(root);
schedulePendingInteractions(root, expirationTime);
if (executionContext === NoContext) {
// Flush the synchronous work now, unless we're already working or inside
// a batch. This is intentionally inside scheduleUpdateOnFiber instead of
// scheduleCallbackForFiber to preserve the ability to schedule a callback
// without immediately flushing it. We only do this for user-initiated
// updates, to preserve historical behavior of legacy mode.
flushSyncCallbackQueue();
}
}
} else {
ensureRootIsScheduled(root);
schedulePendingInteractions(root, expirationTime);
}
// 初始渲染不执行
if (
(executionContext & DiscreteEventContext) !== NoContext &&
// Only updates at user-blocking priority or greater are considered
// discrete, even inside a discrete event.
(priorityLevel === UserBlockingPriority ||
priorityLevel === ImmediatePriority)
) {
// This is the result of a discrete event. Track the lowest priority
// discrete update per root so we can flush them early, if needed.
if (rootsWithPendingDiscreteUpdates === null) {
rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);
} else {
const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
if (lastDiscreteTime === undefined || lastDiscreteTime > expirationTime) {
rootsWithPendingDiscreteUpdates.set(root, expirationTime);
}
}
}
}
// 过期时间的赋值到 Root 上
// This is split into a separate function so we can mark a fiber with pending
// work without treating it as a typical update that originates from an event;
// e.g. retrying a Suspense boundary isn't an update, but it does schedule work
// on a fiber.
function markUpdateTimeFromFiberToRoot(fiber, expirationTime) {
// Update the source fiber's expiration time
if (fiber.expirationTime < expirationTime) {
fiber.expirationTime = expirationTime;
}
let alternate = fiber.alternate;
if (alternate !== null && alternate.expirationTime < expirationTime) {
alternate.expirationTime = expirationTime;
}
// Walk the parent path to the root and update the child expiration time.
let node = fiber.return;
let root = null;
if (node === null && fiber.tag === HostRoot) {
root = fiber.stateNode;
} else {
while (node !== null) {
alternate = node.alternate;
if (node.childExpirationTime < expirationTime) {
node.childExpirationTime = expirationTime;
if (
alternate !== null &&
alternate.childExpirationTime < expirationTime
) {
alternate.childExpirationTime = expirationTime;
}
} else if (
alternate !== null &&
alternate.childExpirationTime < expirationTime
) {
alternate.childExpirationTime = expirationTime;
}
if (node.return === null && node.tag === HostRoot) {
root = node.stateNode;
break;
}
node = node.return;
}
}
if (root !== null) {
if (workInProgressRoot === root) {
// Received an update to a tree that's in the middle of rendering. Mark
// that's unprocessed work on this root.
markUnprocessedUpdateTime(expirationTime);
if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
// The root already suspended with a delay, which means this render
// definitely won't finish. Since we have a new update, let's mark it as
// suspended now, right before marking the incoming update. This has the
// effect of interrupting the current render and switching to the update.
// TODO: This happens to work when receiving an update during the render
// phase, because of the trick inside computeExpirationForFiber to
// subtract 1 from `renderExpirationTime` to move it into a
// separate bucket. But we should probably model it with an exception,
// using the same mechanism we use to force hydration of a subtree.
// TODO: This does not account for low pri updates that were already
// scheduled before the root started rendering. Need to track the next
// pending expiration time (perhaps by backtracking the return path) and
// then trigger a restart in the `renderDidSuspendDelayIfPossible` path.
markRootSuspendedAtTime(root, renderExpirationTime);
}
}
// Mark that the root has a pending update.
markRootUpdatedAtTime(root, expirationTime);
}
return root;
}
// 更新 Root.firstPendingTime , firstSuspendedTime 时间
export function markRootUpdatedAtTime(
root: FiberRoot,
expirationTime: ExpirationTime,
): void {
// Update the range of pending times
const firstPendingTime = root.firstPendingTime;
if (expirationTime > firstPendingTime) {
root.firstPendingTime = expirationTime;
}
// Update the range of suspended times. Treat everything lower priority or
// equal to this update as unsuspended.
const firstSuspendedTime = root.firstSuspendedTime;
if (firstSuspendedTime !== NoWork) {
if (expirationTime >= firstSuspendedTime) {
// The entire suspended range is now unsuspended.
root.firstSuspendedTime = root.lastSuspendedTime = root.nextKnownPendingLevel = NoWork;
} else if (expirationTime >= root.lastSuspendedTime) {
root.lastSuspendedTime = expirationTime + 1;
}
// This is a pending level. Check if it's higher priority than the next
// known pending level.
if (expirationTime > root.nextKnownPendingLevel) {
root.nextKnownPendingLevel = expirationTime;
}
}
}
10. 进入 render 阶段, 构建 workInProgress Fiber 树
// 进入 render 阶段, 构建 workInProgress Fiber 树
function performSyncWorkOnRoot(root) {
// 参数 root 为 fiberRoot 对象
// 检查是否有过期的任务
// 如果没有过期的任务 值为 0
// 初始化渲染没有过期的任务待执行
const lastExpiredTime = root.lastExpiredTime;
// NoWork 值为 0
// 如果有过期的任务 将过期时间设置为 lastExpiredTime 否则将过期时间设置为 Sync
// 初始渲染过期时间被设置成了 Sync
const expirationTime = lastExpiredTime !== NoWork ? lastExpiredTime : Sync;
invariant(
(executionContext & (RenderContext | CommitContext)) === NoContext,
'Should not already be working.',
);
// 处理 useEffect
flushPassiveEffects();
// 如果 root 和 workInProgressRoot 不相等
// 说明 workInProgressRoot 不存在, 说明还没有构建 workInProgress Fiber 树
// workInProgressRoot 为全局变量 默认值为 null, 初始渲染时值为 null
// expirationTime => 1073741823
// renderExpirationTime => 0
// true
if (root !== workInProgressRoot || expirationTime !== renderExpirationTime) {
// 构建 workInProgressFiber 树及 rootFiber
prepareFreshStack(root, expirationTime);
// 初始渲染不执行 内部条件判断不成立
startWorkOnPendingInteractions(root, expirationTime);
}
// workInProgress 如果不为 null
if (workInProgress !== null) {
const prevExecutionContext = executionContext;
executionContext |= RenderContext;
const prevDispatcher = pushDispatcher(root);
const prevInteractions = pushInteractions(root);
startWorkLoopTimer(workInProgress);
do {
try {
// 以同步的方式开始构建 Fiber 对象
workLoopSync();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
resetContextDependencies();
executionContext = prevExecutionContext;
popDispatcher(prevDispatcher);
if (enableSchedulerTracing) {
popInteractions(((prevInteractions: any): Set<Interaction>));
}
// 初始渲染 不执行
if (workInProgressRootExitStatus === RootFatalErrored) {
const fatalError = workInProgressRootFatalError;
stopInterruptedWorkLoopTimer();
prepareFreshStack(root, expirationTime);
markRootSuspendedAtTime(root, expirationTime);
ensureRootIsScheduled(root);
throw fatalError;
}
if (workInProgress !== null) {
// 这是一个同步渲染, 所以我们应该完成整棵树
// 无法提交不完整的 root, 此错误可能是由于React中的错误所致. 请提出问题.
invariant(
false,
'Cannot commit an incomplete root. This error is likely caused by a ' +
'bug in React. Please file an issue.',
);
} else {
// We now have a consistent tree. Because this is a sync render, we
// will commit it even if something suspended.
stopFinishedWorkLoopTimer();
// 将构建好的新 Fiber 对象存储在 finishedWork 属性中
// 提交阶段使用
root.finishedWork = (root.current.alternate: any);
root.finishedExpirationTime = expirationTime;
// 结束 render 阶段
// 进入 commit 阶段
finishSyncRender(root);
}
// Before exiting, make sure there's a callback scheduled for the next
// pending level.
ensureRootIsScheduled(root);
}
return null;
}
/**
* 构建 workInProgressFiber 树及 rootFiber
*/
function prepareFreshStack(root, expirationTime) {
// 为 FiberRoot 对象添加 finishedWork 属性
// finishedWork 表示 render 阶段执行完成后构建的待提交的 Fiber 对象
root.finishedWork = null;
// 初始化 finishedExpirationTime 值为 0
root.finishedExpirationTime = NoWork;
const timeoutHandle = root.timeoutHandle;
// 初始化渲染不执行 timeoutHandle => -1 noTimeout => -1
if (timeoutHandle !== noTimeout) {
// The root previous suspended and scheduled a timeout to commit a fallback
// state. Now that we have additional work, cancel the timeout.
root.timeoutHandle = noTimeout;
// $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
cancelTimeout(timeoutHandle);
}
// 初始化渲染不执行 workInProgress 全局变量 初始化为 null
// false
if (workInProgress !== null) {
let interruptedWork = workInProgress.return;
while (interruptedWork !== null) {
unwindInterruptedWork(interruptedWork);
interruptedWork = interruptedWork.return;
}
}
// 建构 workInProgress Fiber 树的 fiberRoot 对象
workInProgressRoot = root;
// 构建 workInProgress Fiber 树中的 rootFiber
workInProgress = createWorkInProgress(root.current, null);
renderExpirationTime = expirationTime;
workInProgressRootExitStatus = RootIncomplete;
workInProgressRootFatalError = null;
workInProgressRootLatestProcessedExpirationTime = Sync;
workInProgressRootLatestSuspenseTimeout = Sync;
workInProgressRootCanSuspendUsingConfig = null;
workInProgressRootNextUnprocessedUpdateTime = NoWork;
workInProgressRootHasPendingPing = false;
// true
if (enableSchedulerTracing) {
spawnedWorkDuringRender = null;
}
if (__DEV__) {
ReactStrictModeWarnings.discardPendingWarnings();
}
}
// 构建 workInProgress Fiber 树中的 rootFiber
// 构建完成后会替换 current fiber
// 初始渲染 pendingProps 为 null
// workInProgress = RootFirber.alternate 我们可以知道 React 创建了 两个 Firber , 一个是CurrentFirber,一个是workInProgress,通过 alternate 是将内存中的 构建完成后最终给到 currentFirber。
// workInProgress 和 currentFirber 互相使用 alternate 存储 对方。
export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
// current: current Fiber 中的 rootFiber
// 获取 current Fiber 对应的 workInProgress Fiber
let workInProgress = current.alternate;
// 如果 workInProgress 不存在
if (workInProgress === null) {
// 创建 fiber 对象
workInProgress = createFiber(
current.tag,
pendingProps,
current.key,
current.mode,
);
// 属性复用
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
if (__DEV__) {
// DEV-only fields
if (enableUserTimingAPI) {
workInProgress._debugID = current._debugID;
}
workInProgress._debugSource = current._debugSource;
workInProgress._debugOwner = current._debugOwner;
workInProgress._debugHookTypes = current._debugHookTypes;
}
// 使用 alternate 存储 current
workInProgress.alternate = current;
// 使用 alternate 存储 workInProgress
current.alternate = workInProgress;
} else {
workInProgress.pendingProps = pendingProps;
// We already have an alternate.
// Reset the effect tag.
workInProgress.effectTag = NoEffect;
// The effect list is no longer valid.
workInProgress.nextEffect = null;
workInProgress.firstEffect = null;
workInProgress.lastEffect = null;
if (enableProfilerTimer) {
// We intentionally reset, rather than copy, actualDuration & actualStartTime.
// This prevents time from endlessly accumulating in new commits.
// This has the downside of resetting values for different priority renders,
// But works for yielding (the common case) and should support resuming.
workInProgress.actualDuration = 0;
workInProgress.actualStartTime = -1;
}
}
workInProgress.childExpirationTime = current.childExpirationTime;
workInProgress.expirationTime = current.expirationTime;
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;
// Clone the dependencies object. This is mutated during the render phase, so
// it cannot be shared with the current fiber.
const currentDependencies = current.dependencies;
workInProgress.dependencies =
currentDependencies === null
? null
: {
expirationTime: currentDependencies.expirationTime,
firstContext: currentDependencies.firstContext,
responders: currentDependencies.responders,
};
// These will be overridden during the parent's reconciliation
workInProgress.sibling = current.sibling;
workInProgress.index = current.index;
workInProgress.ref = current.ref;
if (enableProfilerTimer) {
workInProgress.selfBaseDuration = current.selfBaseDuration;
workInProgress.treeBaseDuration = current.treeBaseDuration;
}
if (__DEV__) {
workInProgress._debugNeedsRemount = current._debugNeedsRemount;
switch (workInProgress.tag) {
case IndeterminateComponent:
case FunctionComponent:
case SimpleMemoComponent:
workInProgress.type = resolveFunctionForHotReloading(current.type);
break;
case ClassComponent:
workInProgress.type = resolveClassForHotReloading(current.type);
break;
case ForwardRef:
workInProgress.type = resolveForwardRefForHotReloading(current.type);
break;
default:
break;
}
}
return workInProgress;
}
11. 构建 rootFiber 下的所有 Fiber对象,从父到子去构建,判断其类型 是属于 Fiber 的什么类型,例如:函数,类,Root, 字符串等
function workLoopSync() {
// workInProgress 是一个 fiber 对象
// 它的值不为 null 意味着该 fiber 对象上仍然有更新要执行
// while 方法支撑 render 阶段 所有 fiber 节点的构建
while (workInProgress !== null) {
workInProgress = performUnitOfWork(workInProgress);
}
}
// 构建 Fiber 对象
function performUnitOfWork(unitOfWork: Fiber): Fiber | null {
// unitOfWork => workInProgress Fiber 树中的 rootFiber
// current => currentFiber 树中的 rootFiber
const current = unitOfWork.alternate;
startWorkTimer(unitOfWork);
// 开发环境执行 忽略
setCurrentDebugFiberInDEV(unitOfWork);
// 存储下一个要构建的子级 Fiber 对象
let next;
// 初始渲染 不执行
// false
if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
startProfilerTimer(unitOfWork);
next = beginWork(current, unitOfWork, renderExpirationTime);
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
} else {
// beginWork: 从父到子, 构建 Fiber 节点对象
// 返回值 next 为当前节点的子节点
next = beginWork(current, unitOfWork, renderExpirationTime);
}
// 开发环境执行 忽略
resetCurrentDebugFiberInDEV();
// 为旧的 props 属性赋值
// 此次更新后 pendingProps 变为 memoizedProps
unitOfWork.memoizedProps = unitOfWork.pendingProps;
// 如果子节点不存在说明当前节点向下遍历子节点已经到底了
// 继续向上返回 遇到兄弟节点 构建兄弟节点的子 Fiber 对象 直到返回到根 Fiber 对象
if (next === null) {
// 从子到父, 构建其余节点 Fiber 对象
next = completeUnitOfWork(unitOfWork);
}
ReactCurrentOwner.current = null;
return next;
}
// 从父到子, 构建 Fiber 节点对象
function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {
// 1073741823
const updateExpirationTime = workInProgress.expirationTime;
if (__DEV__) {
if (workInProgress._debugNeedsRemount && current !== null) {
// This will restart the begin phase with a new fiber.
return remountFiber(
current,
workInProgress,
createFiberFromTypeAndProps(
workInProgress.type,
workInProgress.key,
workInProgress.pendingProps,
workInProgress._debugOwner || null,
workInProgress.mode,
workInProgress.expirationTime,
),
);
}
}
// 判断是否有旧的 Fiber 对象
// 初始渲染时 只有 rootFiber 节点存在 current
if (current !== null) {
// 获取旧的 props 对象
const oldProps = current.memoizedProps;
// 获取新的 props 对象
const newProps = workInProgress.pendingProps;
// 初始渲染时 false
if (
oldProps !== newProps ||
hasLegacyContextChanged() ||
// Force a re-render if the implementation changed due to hot reload:
(__DEV__ ? workInProgress.type !== current.type : false)
) {
// If props or context changed, mark the fiber as having performed work.
// This may be unset if the props are determined to be equal later (memo).
didReceiveUpdate = true;
// 初始渲染时 false 两个值相同
} else if (updateExpirationTime < renderExpirationTime) {
// 初始渲染时 false
// 可以直接复用前一次更新的子Fiber, 不需要新建子Fiber
didReceiveUpdate = false;
// This fiber does not have any pending work. Bailout without entering
// the begin phase. There's still some bookkeeping we that needs to be done
// in this optimized path, mostly pushing stuff onto the stack.
switch (workInProgress.tag) {
case HostRoot:
pushHostRootContext(workInProgress);
resetHydrationState();
break;
case HostComponent:
pushHostContext(workInProgress);
if (
workInProgress.mode & ConcurrentMode &&
renderExpirationTime !== Never &&
shouldDeprioritizeSubtree(workInProgress.type, newProps)
) {
if (enableSchedulerTracing) {
markSpawnedWork(Never);
}
// Schedule this fiber to re-render at offscreen priority. Then bailout.
workInProgress.expirationTime = workInProgress.childExpirationTime = Never;
return null;
}
break;
case ClassComponent: {
const Component = workInProgress.type;
if (isLegacyContextProvider(Component)) {
pushLegacyContextProvider(workInProgress);
}
break;
}
case HostPortal:
pushHostContainer(
workInProgress,
workInProgress.stateNode.containerInfo,
);
break;
case ContextProvider: {
const newValue = workInProgress.memoizedProps.value;
pushProvider(workInProgress, newValue);
break;
}
case Profiler:
if (enableProfilerTimer) {
// Profiler should only call onRender when one of its descendants actually rendered.
const hasChildWork =
workInProgress.childExpirationTime >= renderExpirationTime;
if (hasChildWork) {
workInProgress.effectTag |= Update;
}
}
break;
case SuspenseComponent: {
const state: SuspenseState | null = workInProgress.memoizedState;
if (state !== null) {
if (enableSuspenseServerRenderer) {
if (state.dehydrated !== null) {
pushSuspenseContext(
workInProgress,
setDefaultShallowSuspenseContext(suspenseStackCursor.current),
);
// We know that this component will suspend again because if it has
// been unsuspended it has committed as a resolved Suspense component.
// If it needs to be retried, it should have work scheduled on it.
workInProgress.effectTag |= DidCapture;
break;
}
}
// If this boundary is currently timed out, we need to decide
// whether to retry the primary children, or to skip over it and
// go straight to the fallback. Check the priority of the primary
// child fragment.
const primaryChildFragment: Fiber = (workInProgress.child: any);
const primaryChildExpirationTime =
primaryChildFragment.childExpirationTime;
if (
primaryChildExpirationTime !== NoWork &&
primaryChildExpirationTime >= renderExpirationTime
) {
// The primary children have pending work. Use the normal path
// to attempt to render the primary children again.
return updateSuspenseComponent(
current,
workInProgress,
renderExpirationTime,
);
} else {
pushSuspenseContext(
workInProgress,
setDefaultShallowSuspenseContext(suspenseStackCursor.current),
);
// The primary children do not have pending work with sufficient
// priority. Bailout.
const child = bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
if (child !== null) {
// The fallback children have pending work. Skip over the
// primary children and work on the fallback.
return child.sibling;
} else {
return null;
}
}
} else {
pushSuspenseContext(
workInProgress,
setDefaultShallowSuspenseContext(suspenseStackCursor.current),
);
}
break;
}
case SuspenseListComponent: {
const didSuspendBefore =
(current.effectTag & DidCapture) !== NoEffect;
const hasChildWork =
workInProgress.childExpirationTime >= renderExpirationTime;
if (didSuspendBefore) {
if (hasChildWork) {
// If something was in fallback state last time, and we have all the
// same children then we're still in progressive loading state.
// Something might get unblocked by state updates or retries in the
// tree which will affect the tail. So we need to use the normal
// path to compute the correct tail.
return updateSuspenseListComponent(
current,
workInProgress,
renderExpirationTime,
);
}
// If none of the children had any work, that means that none of
// them got retried so they'll still be blocked in the same way
// as before. We can fast bail out.
workInProgress.effectTag |= DidCapture;
}
// If nothing suspended before and we're rendering the same children,
// then the tail doesn't matter. Anything new that suspends will work
// in the "together" mode, so we can continue from the state we had.
let renderState = workInProgress.memoizedState;
if (renderState !== null) {
// Reset to the "together" mode in case we've started a different
// update in the past but didn't complete it.
renderState.rendering = null;
renderState.tail = null;
}
pushSuspenseContext(workInProgress, suspenseStackCursor.current);
if (hasChildWork) {
break;
} else {
// If none of the children had any work, that means that none of
// them got retried so they'll still be blocked in the same way
// as before. We can fast bail out.
return null;
}
}
}
// 复用 current
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
} else {
// An update was scheduled on this fiber, but there are no new props
// nor legacy context. Set this to false. If an update queue or context
// consumer produces a changed value, it will set this to true. Otherwise,
// the component will assume the children have not changed and bail out.
didReceiveUpdate = false;
}
} else {
didReceiveUpdate = false;
}
// NoWork 常量 值为0 清空过期时间
workInProgress.expirationTime = NoWork;
// 根据当前 Fiber 的类型决定如何构建起子级 Fiber 对象
// 文件位置: shared/ReactWorkTags.js
switch (workInProgress.tag) {
// 2
// 函数组件在第一次被渲染时使用
case IndeterminateComponent: {
return mountIndeterminateComponent(
// 旧 Fiber
current,
// 新 Fiber
workInProgress,
// 新 Fiber 的 type 值 初始渲染时是App组件函数
workInProgress.type,
// 同步 整数最大值 1073741823
renderExpirationTime,
);
}
// 16
case LazyComponent: {
const elementType = workInProgress.elementType;
return mountLazyComponent(
current,
workInProgress,
elementType,
updateExpirationTime,
renderExpirationTime,
);
}
// 0
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,
renderExpirationTime,
);
}
// 1
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,
renderExpirationTime,
);
}
// 3
case HostRoot:
return updateHostRoot(current, workInProgress, renderExpirationTime);
// 5
case HostComponent:
return updateHostComponent(current, workInProgress, renderExpirationTime);
// 6
case HostText:
return updateHostText(current, workInProgress);
// 13
case SuspenseComponent:
return updateSuspenseComponent(
current,
workInProgress,
renderExpirationTime,
);
// 4
case HostPortal:
return updatePortalComponent(
current,
workInProgress,
renderExpirationTime,
);
// 11
case ForwardRef: {
const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === type
? unresolvedProps
: resolveDefaultProps(type, unresolvedProps);
return updateForwardRef(
current,
workInProgress,
type,
resolvedProps,
renderExpirationTime,
);
}
// 7
case Fragment:
return updateFragment(current, workInProgress, renderExpirationTime);
// 8
case Mode:
return updateMode(current, workInProgress, renderExpirationTime);
// 12
case Profiler:
return updateProfiler(current, workInProgress, renderExpirationTime);
// 10
case ContextProvider:
return updateContextProvider(
current,
workInProgress,
renderExpirationTime,
);
// 9
case ContextConsumer:
return updateContextConsumer(
current,
workInProgress,
renderExpirationTime,
);
// 14
case MemoComponent: {
const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
// Resolve outer props first, then resolve inner props.
let resolvedProps = resolveDefaultProps(type, unresolvedProps);
if (__DEV__) {
if (workInProgress.type !== workInProgress.elementType) {
const outerPropTypes = type.propTypes;
if (outerPropTypes) {
checkPropTypes(
outerPropTypes,
resolvedProps, // Resolved for outer only
'prop',
getComponentName(type),
getCurrentFiberStackInDev,
);
}
}
}
resolvedProps = resolveDefaultProps(type.type, resolvedProps);
return updateMemoComponent(
current,
workInProgress,
type,
resolvedProps,
updateExpirationTime,
renderExpirationTime,
);
}
// 15
case SimpleMemoComponent: {
return updateSimpleMemoComponent(
current,
workInProgress,
workInProgress.type,
workInProgress.pendingProps,
updateExpirationTime,
renderExpirationTime,
);
}
// 17
case IncompleteClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return mountIncompleteClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderExpirationTime,
);
}
// 19
case SuspenseListComponent: {
return updateSuspenseListComponent(
current,
workInProgress,
renderExpirationTime,
);
}
// 20
case FundamentalComponent: {
if (enableFundamentalAPI) {
return updateFundamentalComponent(
current,
workInProgress,
renderExpirationTime,
);
}
break;
}
// 21
case ScopeComponent: {
if (enableScopeAPI) {
return updateScopeComponent(
current,
workInProgress,
renderExpirationTime,
);
}
break;
}
// 22
case Block: {
if (enableBlocksAPI) {
const block = workInProgress.type;
const props = workInProgress.pendingProps;
return updateBlock(
current,
workInProgress,
block,
props,
renderExpirationTime,
);
}
break;
}
}
invariant(
false,
'Unknown unit of work tag (%s). This error is likely caused by a bug in ' +
'React. Please file an issue.',
workInProgress.tag,
);
}
12 . 更新 hostRoot 的 Firber 对象 (<div id="root"></div> 对应的 Fiber 对象 )
// 更新 hostRoot
// <div id="root"></div> 对应的 Fiber 对象
function updateHostRoot(current, workInProgress, renderExpirationTime) {
pushHostRootContext(workInProgress);
// 获取更新队列
const updateQueue = workInProgress.updateQueue;
invariant(
current !== null && updateQueue !== null,
'If the root does not have an updateQueue, we should have already ' +
'bailed out. This error is likely caused by a bug in React. Please ' +
'file an issue.',
);
// 获取新的 props 对象 null
const nextProps = workInProgress.pendingProps;
// 获取上一次渲染使用的 state null
const prevState = workInProgress.memoizedState;
// 获取上一次渲染使用的 children null
const prevChildren = prevState !== null ? prevState.element : null;
// 浅复制更新队列, 防止引用属性互相影响
// workInProgress.updateQueue 浅拷贝 current.updateQueue
cloneUpdateQueue(current, workInProgress);
// 获取 updateQueue.payload 并赋值到 workInProgress.memoizedState
// 要更新的内容就是 element 就是 rootFiber 的子元素
processUpdateQueue(workInProgress, nextProps, null, renderExpirationTime);
// 获取 element 所在对象
const nextState = workInProgress.memoizedState;
// 从对象中获取 element
const nextChildren = nextState.element;
// 在计算 state 后如果前后两个 Children 相同的情况
// prevChildren => null
// nextState => App
// false
if (nextChildren === prevChildren) {
// If the state is the same as before, that's a bailout because we had
// no work that expires at this time.
resetHydrationState();
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
// 获取 fiberRoot 对象
const root: FiberRoot = workInProgress.stateNode;
// 服务器端渲染走 if
if (root.hydrate && enterHydrationState(workInProgress)) {
// If we don't have any current children this might be the first pass.
// We always try to hydrate. If this isn't a hydration pass there won't
// be any children to hydrate which is effectively the same thing as
// not hydrating.
let child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderExpirationTime,
);
workInProgress.child = child;
let node = child;
while (node) {
// Mark each child as hydrating. This is a fast path to know whether this
// tree is part of a hydrating tree. This is used to determine if a child
// node has fully mounted yet, and for scheduling event replaying.
// Conceptually this is similar to Placement in that a new subtree is
// inserted into the React tree here. It just happens to not need DOM
// mutations because it already exists.
node.effectTag = (node.effectTag & ~Placement) | Hydrating;
node = node.sibling;
}
} else {
// 客户端渲染走 else
// 构建子节点 fiber 对象
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
resetHydrationState();
}
// 返回子节点 fiber 对象
return workInProgress.child;
}
// 构建子级 Fiber 对象
export function reconcileChildren(
// 旧 Fiber
current: Fiber | null,
// 父级 Fiber
workInProgress: Fiber,
// 子级 vdom 对象
nextChildren: any,
// 初始渲染 整型最大值 代表同步任务
renderExpirationTime: ExpirationTime,
) {
/**
* 为什么要传递 current ?
* 如果不是初始渲染的情况, 要进行新旧 Fiber 对比
* 初始渲染时则用不到 current
*/
// 如果就 Fiber 为 null 表示初始渲染
if (current === null) {
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderExpirationTime,
);
} else {
// 否则就是更新
// If the current child is the same as the work in progress, it means that
// we haven't yet started any work on these children. Therefore, we use
// the clone algorithm to create a copy of all the current children.
// If we had any progressed work already, that is invalid at this point so
// let's throw it out.
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
nextChildren,
renderExpirationTime,
);
}
}
// 用于初始渲染
export const mountChildFibers = ChildReconciler(false);
// This wrapper function exists because I expect to clone the code in each path
// to be able to optimize each path individually by branching early. This needs
// a compiler or we can do it manually. Helpers that don't need this branching
// live outside of this function.
function ChildReconciler(shouldTrackSideEffects) {
function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void {
if (!shouldTrackSideEffects) {
// Noop.
return;
}
// Deletions are added in reversed order so we add it to the front.
// At this point, the return fiber's effect list is empty except for
// deletions, so we can just append the deletion to the list. The remaining
// effects aren't added until the complete phase. Once we implement
// resuming, this may not be true.
const last = returnFiber.lastEffect;
if (last !== null) {
last.nextEffect = childToDelete;
returnFiber.lastEffect = childToDelete;
} else {
returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
}
childToDelete.nextEffect = null;
childToDelete.effectTag = Deletion;
}
function deleteRemainingChildren(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
): null {
if (!shouldTrackSideEffects) {
// Noop.
return null;
}
// TODO: For the shouldClone case, this could be micro-optimized a bit by
// assuming that after the first child we've already added everything.
let childToDelete = currentFirstChild;
while (childToDelete !== null) {
deleteChild(returnFiber, childToDelete);
childToDelete = childToDelete.sibling;
}
return null;
}
function mapRemainingChildren(
returnFiber: Fiber,
currentFirstChild: Fiber,
): Map<string | number, Fiber> {
// Add the remaining children to a temporary map so that we can find them by
// keys quickly. Implicit (null) keys get added to this set with their index
// instead.
const existingChildren: Map<string | number, Fiber> = new Map();
let existingChild = currentFirstChild;
while (existingChild !== null) {
if (existingChild.key !== null) {
existingChildren.set(existingChild.key, existingChild);
} else {
existingChildren.set(existingChild.index, existingChild);
}
existingChild = existingChild.sibling;
}
return existingChildren;
}
function useFiber(fiber: Fiber, pendingProps: mixed): Fiber {
// We currently set sibling to null and index to 0 here because it is easy
// to forget to do before returning it. E.g. for the single child case.
const clone = createWorkInProgress(fiber, pendingProps);
clone.index = 0;
clone.sibling = null;
return clone;
}
function placeChild(
newFiber: Fiber,
lastPlacedIndex: number,
newIndex: number,
): number {
newFiber.index = newIndex;
if (!shouldTrackSideEffects) {
// Noop.
return lastPlacedIndex;
}
const current = newFiber.alternate;
if (current !== null) {
const oldIndex = current.index;
if (oldIndex < lastPlacedIndex) {
// This is a move.
newFiber.effectTag = Placement;
return lastPlacedIndex;
} else {
// This item can stay in place.
return oldIndex;
}
} else {
// This is an insertion.
newFiber.effectTag = Placement;
return lastPlacedIndex;
}
}
function placeSingleChild(newFiber: Fiber): Fiber {
// 如果是初始渲染 会在根组件(App)上设置 effectTag 属性为 Placement 值为 1
// 其他子级节点具有默认值为 0 防止在 commit 阶段反复操作真实DOM
// 初始渲染时如果当前处理的是根组件 true 其他组件 false
if (shouldTrackSideEffects && newFiber.alternate === null) {
// Placement 表示新创建的节点
newFiber.effectTag = Placement;
}
return newFiber;
}
function updateTextNode(
returnFiber: Fiber,
current: Fiber | null,
textContent: string,
expirationTime: ExpirationTime,
) {
if (current === null || current.tag !== HostText) {
// Insert
const created = createFiberFromText(
textContent,
returnFiber.mode,
expirationTime,
);
created.return = returnFiber;
return created;
} else {
// Update
const existing = useFiber(current, textContent);
existing.return = returnFiber;
return existing;
}
}
function updateElement(
returnFiber: Fiber,
current: Fiber | null,
element: ReactElement,
expirationTime: ExpirationTime,
): Fiber {
if (current !== null) {
if (
current.elementType === element.type ||
// Keep this check inline so it only runs on the false path:
(__DEV__ ? isCompatibleFamilyForHotReloading(current, element) : false)
) {
// Move based on index
const existing = useFiber(current, element.props);
existing.ref = coerceRef(returnFiber, current, element);
existing.return = returnFiber;
if (__DEV__) {
existing._debugSource = element._source;
existing._debugOwner = element._owner;
}
return existing;
} else if (
enableBlocksAPI &&
current.tag === Block &&
element.type.$$typeof === REACT_BLOCK_TYPE &&
element.type.render === current.type.render
) {
// Same as above but also update the .type field.
const existing = useFiber(current, element.props);
existing.return = returnFiber;
existing.type = element.type;
if (__DEV__) {
existing._debugSource = element._source;
existing._debugOwner = element._owner;
}
return existing;
}
}
// 创建 Fiber 对象
const created = createFiberFromElement(
element,
returnFiber.mode,
expirationTime,
);
// 添加 ref 属性
created.ref = coerceRef(returnFiber, current, element);
// 添加父级 Fiber 对象
created.return = returnFiber;
// 返回创建的 Fiber 对象
return created;
}
function updatePortal(
returnFiber: Fiber,
current: Fiber | null,
portal: ReactPortal,
expirationTime: ExpirationTime,
): Fiber {
if (
current === null ||
current.tag !== HostPortal ||
current.stateNode.containerInfo !== portal.containerInfo ||
current.stateNode.implementation !== portal.implementation
) {
// Insert
const created = createFiberFromPortal(
portal,
returnFiber.mode,
expirationTime,
);
created.return = returnFiber;
return created;
} else {
// Update
const existing = useFiber(current, portal.children || []);
existing.return = returnFiber;
return existing;
}
}
function updateFragment(
returnFiber: Fiber,
current: Fiber | null,
fragment: Iterable<*>,
expirationTime: ExpirationTime,
key: null | string,
): Fiber {
if (current === null || current.tag !== Fragment) {
// Insert
const created = createFiberFromFragment(
fragment,
returnFiber.mode,
expirationTime,
key,
);
created.return = returnFiber;
return created;
} else {
// Update
const existing = useFiber(current, fragment);
existing.return = returnFiber;
return existing;
}
}
function createChild(
returnFiber: Fiber,
newChild: any,
expirationTime: ExpirationTime,
): Fiber | null {
if (typeof newChild === 'string' || typeof newChild === 'number') {
// Text nodes don't have keys. If the previous node is implicitly keyed
// we can continue to replace it without aborting even if it is not a text
// node.
const created = createFiberFromText(
'' + newChild,
returnFiber.mode,
expirationTime,
);
created.return = returnFiber;
return created;
}
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
const created = createFiberFromElement(
newChild,
returnFiber.mode,
expirationTime,
);
created.ref = coerceRef(returnFiber, null, newChild);
created.return = returnFiber;
return created;
}
case REACT_PORTAL_TYPE: {
const created = createFiberFromPortal(
newChild,
returnFiber.mode,
expirationTime,
);
created.return = returnFiber;
return created;
}
}
if (isArray(newChild) || getIteratorFn(newChild)) {
const created = createFiberFromFragment(
newChild,
returnFiber.mode,
expirationTime,
null,
);
created.return = returnFiber;
return created;
}
throwOnInvalidObjectType(returnFiber, newChild);
}
if (__DEV__) {
if (typeof newChild === 'function') {
warnOnFunctionType();
}
}
return null;
}
function updateSlot(
returnFiber: Fiber,
oldFiber: Fiber | null,
newChild: any,
expirationTime: ExpirationTime,
): Fiber | null {
// Update the fiber if the keys match, otherwise return null.
const key = oldFiber !== null ? oldFiber.key : null;
if (typeof newChild === 'string' || typeof newChild === 'number') {
// Text nodes don't have keys. If the previous node is implicitly keyed
// we can continue to replace it without aborting even if it is not a text
// node.
if (key !== null) {
return null;
}
return updateTextNode(
returnFiber,
oldFiber,
'' + newChild,
expirationTime,
);
}
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
if (newChild.key === key) {
if (newChild.type === REACT_FRAGMENT_TYPE) {
return updateFragment(
returnFiber,
oldFiber,
newChild.props.children,
expirationTime,
key,
);
}
return updateElement(
returnFiber,
oldFiber,
newChild,
expirationTime,
);
} else {
return null;
}
}
case REACT_PORTAL_TYPE: {
if (newChild.key === key) {
return updatePortal(
returnFiber,
oldFiber,
newChild,
expirationTime,
);
} else {
return null;
}
}
}
if (isArray(newChild) || getIteratorFn(newChild)) {
if (key !== null) {
return null;
}
return updateFragment(
returnFiber,
oldFiber,
newChild,
expirationTime,
null,
);
}
throwOnInvalidObjectType(returnFiber, newChild);
}
if (__DEV__) {
if (typeof newChild === 'function') {
warnOnFunctionType();
}
}
return null;
}
function updateFromMap(
existingChildren: Map<string | number, Fiber>,
returnFiber: Fiber,
newIdx: number,
newChild: any,
expirationTime: ExpirationTime,
): Fiber | null {
if (typeof newChild === 'string' || typeof newChild === 'number') {
// Text nodes don't have keys, so we neither have to check the old nor
// new node for the key. If both are text nodes, they match.
const matchedFiber = existingChildren.get(newIdx) || null;
return updateTextNode(
returnFiber,
matchedFiber,
'' + newChild,
expirationTime,
);
}
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
const matchedFiber =
existingChildren.get(
newChild.key === null ? newIdx : newChild.key,
) || null;
if (newChild.type === REACT_FRAGMENT_TYPE) {
return updateFragment(
returnFiber,
matchedFiber,
newChild.props.children,
expirationTime,
newChild.key,
);
}
return updateElement(
returnFiber,
matchedFiber,
newChild,
expirationTime,
);
}
case REACT_PORTAL_TYPE: {
const matchedFiber =
existingChildren.get(
newChild.key === null ? newIdx : newChild.key,
) || null;
return updatePortal(
returnFiber,
matchedFiber,
newChild,
expirationTime,
);
}
}
if (isArray(newChild) || getIteratorFn(newChild)) {
const matchedFiber = existingChildren.get(newIdx) || null;
return updateFragment(
returnFiber,
matchedFiber,
newChild,
expirationTime,
null,
);
}
throwOnInvalidObjectType(returnFiber, newChild);
}
if (__DEV__) {
if (typeof newChild === 'function') {
warnOnFunctionType();
}
}
return null;
}
/**
* Warns if there is a duplicate or missing key
*/
function warnOnInvalidKey(
child: mixed,
knownKeys: Set<string> | null,
): Set<string> | null {
if (__DEV__) {
if (typeof child !== 'object' || child === null) {
return knownKeys;
}
switch (child.$$typeof) {
case REACT_ELEMENT_TYPE:
case REACT_PORTAL_TYPE:
warnForMissingKey(child);
const key = child.key;
if (typeof key !== 'string') {
break;
}
if (knownKeys === null) {
knownKeys = new Set();
knownKeys.add(key);
break;
}
if (!knownKeys.has(key)) {
knownKeys.add(key);
break;
}
console.error(
'Encountered two children with the same key, `%s`. ' +
'Keys should be unique so that components maintain their identity ' +
'across updates. Non-unique keys may cause children to be ' +
'duplicated and/or omitted — the behavior is unsupported and ' +
'could change in a future version.',
key,
);
break;
default:
break;
}
}
return knownKeys;
}
// 处理子元素是数组的情况
function reconcileChildrenArray(
// 父级 Fiber
returnFiber: Fiber,
currentFirstChild: Fiber | null,
// 子级 vdom 数组
newChildren: Array<*>,
expirationTime: ExpirationTime,
): Fiber | null {
if (__DEV__) {
// First, validate keys.
let knownKeys = null;
for (let i = 0; i < newChildren.length; i++) {
const child = newChildren[i];
knownKeys = warnOnInvalidKey(child, knownKeys);
}
}
/**
* 存储第一个子节点 Fiber 对象
* 方法返回的也是第一个子节点 Fiber 对象
* 因为其他子节点 Fiber 对象都存储在上一个子 Fiber 节点对象的 sibling 属性中
*/
let resultingFirstChild: Fiber | null = null;
// 上一次创建的 Fiber 对象
let previousNewFiber: Fiber | null = null;
// 初始渲染没有旧的子级 所以为 null
let oldFiber = currentFirstChild;
let lastPlacedIndex = 0;
let newIdx = 0;
let nextOldFiber = null;
// 初始渲染 oldFiber 为 null 循环不执行
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
if (oldFiber.index > newIdx) {
nextOldFiber = oldFiber;
oldFiber = null;
} else {
nextOldFiber = oldFiber.sibling;
}
const newFiber = updateSlot(
returnFiber,
oldFiber,
newChildren[newIdx],
expirationTime,
);
if (newFiber === null) {
// TODO: This breaks on empty slots like null children. That's
// unfortunate because it triggers the slow path all the time. We need
// a better way to communicate whether this was a miss or null,
// boolean, undefined, etc.
if (oldFiber === null) {
oldFiber = nextOldFiber;
}
break;
}
if (shouldTrackSideEffects) {
if (oldFiber && newFiber.alternate === null) {
// We matched the slot, but we didn't reuse the existing fiber, so we
// need to delete the existing child.
deleteChild(returnFiber, oldFiber);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
// TODO: Move out of the loop. This only happens for the first run.
resultingFirstChild = newFiber;
} else {
// TODO: Defer siblings if we're not at the right index for this slot.
// I.e. if we had null values before, then we want to defer this
// for each null value. However, we also don't want to call updateSlot
// with the previous one.
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
oldFiber = nextOldFiber;
}
// 初始渲染不执行
if (newIdx === newChildren.length) {
// We've reached the end of the new children. We can delete the rest.
deleteRemainingChildren(returnFiber, oldFiber);
return resultingFirstChild;
}
// oldFiber 为空 说明是初始渲染
if (oldFiber === null) {
// 遍历子 vdom 对象
for (; newIdx < newChildren.length; newIdx++) {
// 创建子 vdom 对应的 fiber 对象
const newFiber = createChild(
returnFiber,
newChildren[newIdx],
expirationTime,
);
// 如果 newFiber 为 null
if (newFiber === null) {
// 进入下次循环
continue;
}
// 初始渲染时只为 newFiber 添加了 index 属性,
// 其他事没干. lastPlacedIndex 被原封不动的返回了
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
// 为当前节点设置下一个兄弟节点
if (previousNewFiber === null) {
// 存储第一个子 Fiber 发生在第一次循环时
resultingFirstChild = newFiber;
} else {
// 为节点设置下一个兄弟 Fiber
previousNewFiber.sibling = newFiber;
}
// 在循环的过程中更新上一个创建的Fiber 对象
previousNewFiber = newFiber;
}
// 返回创建好的子 Fiber
// 其他 Fiber 都作为 sibling 存在
return resultingFirstChild;
}
// 下面的代码初始渲染不执行
// Add all children to a key map for quick lookups.
const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
// Keep scanning and use the map to restore deleted items as moves.
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = updateFromMap(
existingChildren,
returnFiber,
newIdx,
newChildren[newIdx],
expirationTime,
);
if (newFiber !== null) {
if (shouldTrackSideEffects) {
if (newFiber.alternate !== null) {
// The new fiber is a work in progress, but if there exists a
// current, that means that we reused the fiber. We need to delete
// it from the child list so that we don't add it to the deletion
// list.
existingChildren.delete(
newFiber.key === null ? newIdx : newFiber.key,
);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
}
// 初始渲染不执行
if (shouldTrackSideEffects) {
// Any existing children that weren't consumed above were deleted. We need
// to add them to the deletion list.
existingChildren.forEach((child) => deleteChild(returnFiber, child));
}
// 返回第一个子元素 Fiber 对象
return resultingFirstChild;
}
function reconcileChildrenIterator(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChildrenIterable: Iterable<*>,
expirationTime: ExpirationTime,
): Fiber | null {
// This is the same implementation as reconcileChildrenArray(),
// but using the iterator instead.
const iteratorFn = getIteratorFn(newChildrenIterable);
invariant(
typeof iteratorFn === 'function',
'An object is not an iterable. This error is likely caused by a bug in ' +
'React. Please file an issue.',
);
if (__DEV__) {
// We don't support rendering Generators because it's a mutation.
// See https://github.com/facebook/react/issues/12995
if (
typeof Symbol === 'function' &&
// $FlowFixMe Flow doesn't know about toStringTag
newChildrenIterable[Symbol.toStringTag] === 'Generator'
) {
if (!didWarnAboutGenerators) {
console.error(
'Using Generators as children is unsupported and will likely yield ' +
'unexpected results because enumerating a generator mutates it. ' +
'You may convert it to an array with `Array.from()` or the ' +
'`[...spread]` operator before rendering. Keep in mind ' +
'you might need to polyfill these features for older browsers.',
);
}
didWarnAboutGenerators = true;
}
// Warn about using Maps as children
if ((newChildrenIterable: any).entries === iteratorFn) {
if (!didWarnAboutMaps) {
console.error(
'Using Maps as children is unsupported and will likely yield ' +
'unexpected results. Convert it to a sequence/iterable of keyed ' +
'ReactElements instead.',
);
}
didWarnAboutMaps = true;
}
// First, validate keys.
// We'll get a different iterator later for the main pass.
const newChildren = iteratorFn.call(newChildrenIterable);
if (newChildren) {
let knownKeys = null;
let step = newChildren.next();
for (; !step.done; step = newChildren.next()) {
const child = step.value;
knownKeys = warnOnInvalidKey(child, knownKeys);
}
}
}
const newChildren = iteratorFn.call(newChildrenIterable);
invariant(newChildren != null, 'An iterable object provided no iterator.');
let resultingFirstChild: Fiber | null = null;
let previousNewFiber: Fiber | null = null;
let oldFiber = currentFirstChild;
let lastPlacedIndex = 0;
let newIdx = 0;
let nextOldFiber = null;
let step = newChildren.next();
for (
;
oldFiber !== null && !step.done;
newIdx++, step = newChildren.next()
) {
if (oldFiber.index > newIdx) {
nextOldFiber = oldFiber;
oldFiber = null;
} else {
nextOldFiber = oldFiber.sibling;
}
const newFiber = updateSlot(
returnFiber,
oldFiber,
step.value,
expirationTime,
);
if (newFiber === null) {
// TODO: This breaks on empty slots like null children. That's
// unfortunate because it triggers the slow path all the time. We need
// a better way to communicate whether this was a miss or null,
// boolean, undefined, etc.
if (oldFiber === null) {
oldFiber = nextOldFiber;
}
break;
}
if (shouldTrackSideEffects) {
if (oldFiber && newFiber.alternate === null) {
// We matched the slot, but we didn't reuse the existing fiber, so we
// need to delete the existing child.
deleteChild(returnFiber, oldFiber);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
// TODO: Move out of the loop. This only happens for the first run.
resultingFirstChild = newFiber;
} else {
// TODO: Defer siblings if we're not at the right index for this slot.
// I.e. if we had null values before, then we want to defer this
// for each null value. However, we also don't want to call updateSlot
// with the previous one.
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
oldFiber = nextOldFiber;
}
if (step.done) {
// We've reached the end of the new children. We can delete the rest.
deleteRemainingChildren(returnFiber, oldFiber);
return resultingFirstChild;
}
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 (; !step.done; newIdx++, step = newChildren.next()) {
const newFiber = createChild(returnFiber, step.value, expirationTime);
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;
}
// Add all children to a key map for quick lookups.
const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
// Keep scanning and use the map to restore deleted items as moves.
for (; !step.done; newIdx++, step = newChildren.next()) {
const newFiber = updateFromMap(
existingChildren,
returnFiber,
newIdx,
step.value,
expirationTime,
);
if (newFiber !== null) {
if (shouldTrackSideEffects) {
if (newFiber.alternate !== null) {
// The new fiber is a work in progress, but if there exists a
// current, that means that we reused the fiber. We need to delete
// it from the child list so that we don't add it to the deletion
// list.
existingChildren.delete(
newFiber.key === null ? newIdx : newFiber.key,
);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
}
if (shouldTrackSideEffects) {
// Any existing children that weren't consumed above were deleted. We need
// to add them to the deletion list.
existingChildren.forEach((child) => deleteChild(returnFiber, child));
}
return resultingFirstChild;
}
// 处理子元素是文本或者数值的情况
function reconcileSingleTextNode(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
textContent: string,
expirationTime: ExpirationTime,
): Fiber {
// 初始渲染不执行
if (currentFirstChild !== null && currentFirstChild.tag === HostText) {
// We already have an existing node so let's just update it and delete
// the rest.
deleteRemainingChildren(returnFiber, currentFirstChild.sibling);
const existing = useFiber(currentFirstChild, textContent);
existing.return = returnFiber;
return existing;
}
// 现有的第一个子节点不是文本节点,因此我们需要创建一个并删除现有的.
// 初始渲染不执行
deleteRemainingChildren(returnFiber, currentFirstChild);
// 根据文本创建 Fiber 对象
const created = createFiberFromText(
textContent,
returnFiber.mode,
expirationTime,
);
// 设置父 Fiber 对象
created.return = returnFiber;
// 返回创建好的 Fiber 对象
return created;
}
// 处理子元素是单个对象的情况
function reconcileSingleElement(
// 父 Fiber 对象
returnFiber: Fiber,
// 备份子 fiber
currentFirstChild: Fiber | null,
// 子 vdom 对象
element: ReactElement,
expirationTime: ExpirationTime,
): Fiber {
const key = element.key;
let child = currentFirstChild;
// 初始渲染 currentFirstChild 为 null
// false
while (child !== null) {
// TODO: If key === null and child.key === null, then this only applies to
// the first item in the list.
if (child.key === key) {
switch (child.tag) {
case Fragment: {
if (element.type === REACT_FRAGMENT_TYPE) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, element.props.children);
existing.return = returnFiber;
if (__DEV__) {
existing._debugSource = element._source;
existing._debugOwner = element._owner;
}
return existing;
}
break;
}
case Block:
if (enableBlocksAPI) {
if (
element.type.$$typeof === REACT_BLOCK_TYPE &&
element.type.render === child.type.render
) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, element.props);
existing.type = element.type;
existing.return = returnFiber;
if (__DEV__) {
existing._debugSource = element._source;
existing._debugOwner = element._owner;
}
return existing;
}
}
// We intentionally fallthrough here if enableBlocksAPI is not on.
// eslint-disable-next-lined no-fallthrough
default: {
if (
child.elementType === element.type ||
// Keep this check inline so it only runs on the false path:
(__DEV__
? isCompatibleFamilyForHotReloading(child, element)
: false)
) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, element.props);
existing.ref = coerceRef(returnFiber, child, element);
existing.return = returnFiber;
if (__DEV__) {
existing._debugSource = element._source;
existing._debugOwner = element._owner;
}
return existing;
}
break;
}
}
// Didn't match.
deleteRemainingChildren(returnFiber, child);
break;
} else {
deleteChild(returnFiber, child);
}
child = child.sibling;
}
// 查看子 vdom 对象是否表示 fragment
// false
if (element.type === REACT_FRAGMENT_TYPE) {
const created = createFiberFromFragment(
element.props.children,
returnFiber.mode,
expirationTime,
element.key,
);
created.return = returnFiber;
return created;
} else {
// 根据 React Element 创建 Fiber 对象
// 返回创建好的 Fiber 对象
const created = createFiberFromElement(
element,
// 用来表示当前组件下的所有子组件要用处于何种渲染模式
// 文件位置: ./ReactTypeOfMode.js
// 0 同步渲染模式
// 100 异步渲染模式
returnFiber.mode,
expirationTime,
);
// 添加 ref 属性 { current: DOM }
created.ref = coerceRef(returnFiber, currentFirstChild, element);
// 添加父级 Fiber 对象
created.return = returnFiber;
// 返回创建好的子 Fiber
return created;
}
}
function reconcileSinglePortal(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
portal: ReactPortal,
expirationTime: ExpirationTime,
): Fiber {
const key = portal.key;
let child = currentFirstChild;
while (child !== null) {
// TODO: If key === null and child.key === null, then this only applies to
// the first item in the list.
if (child.key === key) {
if (
child.tag === HostPortal &&
child.stateNode.containerInfo === portal.containerInfo &&
child.stateNode.implementation === portal.implementation
) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, portal.children || []);
existing.return = returnFiber;
return existing;
} else {
deleteRemainingChildren(returnFiber, child);
break;
}
} else {
deleteChild(returnFiber, child);
}
child = child.sibling;
}
const created = createFiberFromPortal(
portal,
returnFiber.mode,
expirationTime,
);
created.return = returnFiber;
return created;
}
// This API will tag the children with the side-effect of the reconciliation
// itself. They will be added to the side-effect list as we pass through the
// children and the parent.
function reconcileChildFibers(
// 父 Fiber 对象
returnFiber: Fiber,
// 旧的第一个子 Fiber 初始渲染 null
currentFirstChild: Fiber | null,
// 新的子 vdom 对象
newChild: any,
// 初始渲染 整型最大值 代表同步任务
expirationTime: ExpirationTime,
): Fiber | null {
// 这是入口方法, 根据 newChild 类型进行对应处理
// 判断新的子 vdom 是否为占位组件 比如 <></>
// false
const isUnkeyedTopLevelFragment =
typeof newChild === 'object' &&
newChild !== null &&
newChild.type === REACT_FRAGMENT_TYPE &&
newChild.key === null;
// 如果 newChild 为占位符, 使用 占位符组件的子元素作为 newChild
if (isUnkeyedTopLevelFragment) {
newChild = newChild.props.children;
}
// 检测 newChild 是否为对象类型
const isObject = typeof newChild === 'object' && newChild !== null;
// newChild 是单个对象的情况
if (isObject) {
// 匹配子元素的类型
switch (newChild.$$typeof) {
// 子元素为 ReactElement
case REACT_ELEMENT_TYPE:
// 为 Fiber 对象设置 effectTag 属性
// 返回创建好的子 Fiber
return placeSingleChild(
// 处理单个 React Element 的情况
// 内部会调用其他方法创建对应的 Fiber 对象
reconcileSingleElement(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
),
);
case REACT_PORTAL_TYPE:
return placeSingleChild(
reconcileSinglePortal(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
),
);
}
}
// 处理 children 为文本和数值的情况 return "App works"
if (typeof newChild === 'string' || typeof newChild === 'number') {
return placeSingleChild(
reconcileSingleTextNode(
returnFiber,
currentFirstChild,
// 如果 newChild 是数值, 转换为字符串
'' + newChild,
expirationTime,
),
);
}
// children 是数组的情况
if (isArray(newChild)) {
// 返回创建好的子 Fiber
return reconcileChildrenArray(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
);
}
if (getIteratorFn(newChild)) {
return reconcileChildrenIterator(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
);
}
if (isObject) {
throwOnInvalidObjectType(returnFiber, newChild);
}
if (__DEV__) {
if (typeof newChild === 'function') {
warnOnFunctionType();
}
}
if (typeof newChild === 'undefined' && !isUnkeyedTopLevelFragment) {
// If the new child is undefined, and the return fiber is a composite
// component, throw an error. If Fiber return types are disabled,
// we already threw above.
switch (returnFiber.tag) {
case ClassComponent: {
if (__DEV__) {
const instance = returnFiber.stateNode;
if (instance.render._isMockFunction) {
// We allow auto-mocks to proceed as if they're returning null.
break;
}
}
}
// Intentionally fall through to the next case, which handles both
// functions and classes
// eslint-disable-next-lined no-fallthrough
case FunctionComponent: {
const Component = returnFiber.type;
invariant(
false,
'%s(...): Nothing was returned from render. This usually means a ' +
'return statement is missing. Or, to render nothing, ' +
'return null.',
Component.displayName || Component.name || 'Component',
);
}
}
}
// Remaining cases are all treated as empty.
return deleteRemainingChildren(returnFiber, currentFirstChild);
}
return reconcileChildFibers;
}
// 根据 React Element 创建 Fiber 对象
export function createFiberFromElement(
element: ReactElement,
// 父级Fiber mode 子级需要继承
mode: TypeOfMode,
expirationTime: ExpirationTime,
): Fiber {
let owner = null;
if (__DEV__) {
owner = element._owner;
}
const type = element.type;
const key = element.key;
const pendingProps = element.props;
const fiber = createFiberFromTypeAndProps(
type,
key,
pendingProps,
owner,
mode,
expirationTime,
);
if (__DEV__) {
fiber._debugSource = element._source;
fiber._debugOwner = element._owner;
}
return fiber;
}
export function createFiberFromTypeAndProps(
type: any, // React$ElementType
key: null | string,
pendingProps: any,
owner: null | Fiber,
mode: TypeOfMode,
expirationTime: ExpirationTime,
): Fiber {
let fiber;
let fiberTag = IndeterminateComponent;
// The resolved type is set if we know what the final type will be. I.e. it's not lazy.
let resolvedType = type;
if (typeof type === 'function') {
if (shouldConstruct(type)) {
fiberTag = ClassComponent;
if (__DEV__) {
resolvedType = resolveClassForHotReloading(resolvedType);
}
} else {
if (__DEV__) {
resolvedType = resolveFunctionForHotReloading(resolvedType);
}
}
} else if (typeof type === 'string') {
fiberTag = HostComponent;
} else {
getTag: switch (type) {
case REACT_FRAGMENT_TYPE:
return createFiberFromFragment(
pendingProps.children,
mode,
expirationTime,
key,
);
case REACT_CONCURRENT_MODE_TYPE:
fiberTag = Mode;
mode |= ConcurrentMode | BlockingMode | StrictMode;
break;
case REACT_STRICT_MODE_TYPE:
fiberTag = Mode;
mode |= StrictMode;
break;
case REACT_PROFILER_TYPE:
return createFiberFromProfiler(pendingProps, mode, expirationTime, key);
case REACT_SUSPENSE_TYPE:
return createFiberFromSuspense(pendingProps, mode, expirationTime, key);
case REACT_SUSPENSE_LIST_TYPE:
return createFiberFromSuspenseList(
pendingProps,
mode,
expirationTime,
key,
);
default: {
if (typeof type === 'object' && type !== null) {
switch (type.$$typeof) {
case REACT_PROVIDER_TYPE:
fiberTag = ContextProvider;
break getTag;
case REACT_CONTEXT_TYPE:
// This is a consumer
fiberTag = ContextConsumer;
break getTag;
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;
case REACT_BLOCK_TYPE:
fiberTag = Block;
break getTag;
case REACT_FUNDAMENTAL_TYPE:
if (enableFundamentalAPI) {
return createFiberFromFundamental(
type,
pendingProps,
mode,
expirationTime,
key,
);
}
break;
case REACT_SCOPE_TYPE:
if (enableScopeAPI) {
return createFiberFromScope(
type,
pendingProps,
mode,
expirationTime,
key,
);
}
}
}
let info = '';
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.';
}
const ownerName = owner ? getComponentName(owner.type) : null;
if (ownerName) {
info += '\n\nCheck the render method of `' + ownerName + '`.';
}
}
invariant(
false,
'Element type is invalid: expected a string (for built-in ' +
'components) or a class/function (for composite components) ' +
'but got: %s.%s',
type == null ? type : typeof type,
info,
);
}
}
}
fiber = createFiber(fiberTag, pendingProps, key, mode);
fiber.elementType = type;
fiber.type = resolvedType;
fiber.expirationTime = expirationTime;
return fiber;
}
13. 从父到子,构建父级的 child
14. 从子到父, 构建其余节点 Fiber 对象
/**
* 1. 创建 Fiber 对象
* 2. 创建每一个节点的真实 DOM 对象并将其添加到 stateNode 属性中
* 3. 收集要执行 DOM 操作的 Fiber 节点, 组建 effect 链表结构
*/
function completeUnitOfWork(unitOfWork: Fiber): Fiber | null {
// 为 workInProgress 全局变量重新赋值
workInProgress = unitOfWork;
do {
// 获取备份节点
// 初始化渲染 非根 Fiber 对象没有备份节点 所以 current 为 null
const current = workInProgress.alternate;
// 父级 Fiber 对象, 非根 Fiber 对象都有父级
const returnFiber = workInProgress.return;
// 判断传入的 Fiber 对象是否构建完成, 任务调度相关
// & 是表示位的与运算, 把左右两边的数字转化为二进制
// 然后每一位分别进行比较, 如果相等就为1, 不相等即为0
// 此处应用"位与"运算符的目的是"清零"
// true
if ((workInProgress.effectTag & Incomplete) === NoEffect) {
// 开发环境代码 忽略
setCurrentDebugFiberInDEV(workInProgress);
let next;
// 如果不能使用分析器的 timer, 直接执行 completeWork
// enableProfilerTimer => true
// 但此处无论条件是否成立都会执行 completeWork
if (
!enableProfilerTimer ||
(workInProgress.mode & ProfileMode) === NoMode
) {
// 重点代码(二)
// 创建节点真实 DOM 对象并将其添加到 stateNode 属性中
next = completeWork(current, workInProgress, renderExpirationTime);
} else {
// 否则执行分析器timer, 并执行 completeWork
startProfilerTimer(workInProgress);
// 创建节点真实 DOM 对象并将其添加到 stateNode 属性中
next = completeWork(current, workInProgress, renderExpirationTime);
// Update render duration assuming we didn't error.
stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
}
stopWorkTimer(workInProgress);
resetCurrentDebugFiberInDEV();
resetChildExpirationTime(workInProgress);
// 重点代码(一)
// 如果子级存在
if (next !== null) {
// 返回子级 一直返回到 workLoopSync
// 再重新执行 performUnitOfWork 构建子级 Fiber 节点对象
return next;
}
// 构建 effect 链表结构
// 如果不是根 Fiber 就是 true 否则就是 false
// 将子树和此 Fiber 的所有 effect 附加到父级的 effect 列表中
if (
// 如果父 Fiber 存在 并且
returnFiber !== null &&
// 父 Fiber 对象中的 effectTag 为 0
(returnFiber.effectTag & Incomplete) === NoEffect
) {
// 将子树和此 Fiber 的所有副作用附加到父级的 effect 列表上
// 以下两个判断的作用是搜集子 Fiber的 effect 到父 Fiber
if (returnFiber.firstEffect === null) {
// first
returnFiber.firstEffect = workInProgress.firstEffect;
}
if (workInProgress.lastEffect !== null) {
if (returnFiber.lastEffect !== null) {
// next
returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
}
// last
returnFiber.lastEffect = workInProgress.lastEffect;
}
// 获取副作用标记
// 初始渲染时除[根组件]以外的 Fiber, effectTag 值都为 0, 即不需要执行任何真实DOM操作
// 根组件的 effectTag 值为 3, 即需要将此节点对应的真实DOM对象添加到页面中
const effectTag = workInProgress.effectTag;
// 创建 effect 列表时跳过 NoWork(0) 和 PerformedWork(1) 标记
// PerformedWork 由 React DevTools 读取, 不提交
// 初始渲染时 只有遍历到了根组件 判断条件才能成立, 将 effect 链表添加到 rootFiber
// 初始渲染 FiberRoot 对象中的 firstEffect 和 lastEffect 都是 App 组件
// 因为当所有节点在内存中构建完成后, 只需要一次将所有 DOM 添加到页面中
if (effectTag > PerformedWork) {
// false
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = workInProgress;
} else {
// 为 fiberRoot 添加 firstEffect
returnFiber.firstEffect = workInProgress;
}
// 为 fiberRoot 添加 lastEffect
returnFiber.lastEffect = workInProgress;
}
}
} else {
// 初始渲染不执行
// This fiber did not complete because something threw. Pop values off
// the stack without entering the complete phase. If this is a boundary,
// capture values if possible.
const next = unwindWork(workInProgress, renderExpirationTime);
// Because this fiber did not complete, don't reset its expiration time.
if (
enableProfilerTimer &&
(workInProgress.mode & ProfileMode) !== NoMode
) {
// Record the render duration for the fiber that errored.
stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
// Include the time spent working on failed children before continuing.
let actualDuration = workInProgress.actualDuration;
let child = workInProgress.child;
while (child !== null) {
actualDuration += child.actualDuration;
child = child.sibling;
}
workInProgress.actualDuration = actualDuration;
}
if (next !== null) {
// If completing this work spawned new work, do that next. We'll come
// back here again.
// Since we're restarting, remove anything that is not a host effect
// from the effect tag.
// TODO: The name stopFailedWorkTimer is misleading because Suspense
// also captures and restarts.
stopFailedWorkTimer(workInProgress);
next.effectTag &= HostEffectMask;
return next;
}
stopWorkTimer(workInProgress);
if (returnFiber !== null) {
// Mark the parent fiber as incomplete and clear its effect list.
returnFiber.firstEffect = returnFiber.lastEffect = null;
returnFiber.effectTag |= Incomplete;
}
}
// 获取下一个同级 Fiber 对象
const siblingFiber = workInProgress.sibling;
// 如果下一个同级 Fiber 对象存在
if (siblingFiber !== null) {
// 返回下一个同级 Fiber 对象
return siblingFiber;
}
// 否则退回父级
workInProgress = returnFiber;
} while (workInProgress !== null);
// 当执行到这里的时候, 说明遍历到了 root 节点, 已完成遍历
// 更新 workInProgressRootExitStatus 的状态为 已完成
if (workInProgressRootExitStatus === RootIncomplete) {
workInProgressRootExitStatus = RootCompleted;
}
return null;
}
// 创建节点真实 DOM 对象并将其添加到 stateNode 属性中
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {
// 获取待更新 props
const newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case IndeterminateComponent:
case LazyComponent:
case SimpleMemoComponent:
// 0
case FunctionComponent:
case ForwardRef:
case Fragment:
case Mode:
case Profiler:
case ContextConsumer:
case MemoComponent:
return null;
case ClassComponent: {
const Component = workInProgress.type;
if (isLegacyContextProvider(Component)) {
popLegacyContext(workInProgress);
}
return null;
}
// 3
case HostRoot: {
popHostContainer(workInProgress);
popTopLevelLegacyContextObject(workInProgress);
const fiberRoot = (workInProgress.stateNode: FiberRoot);
if (fiberRoot.pendingContext) {
fiberRoot.context = fiberRoot.pendingContext;
fiberRoot.pendingContext = null;
}
if (current === null || current.child === null) {
// If we hydrated, pop so that we can delete any remaining children
// that weren't hydrated.
let wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
// If we hydrated, then we'll need to schedule an update for
// the commit side-effects on the root.
markUpdate(workInProgress);
}
}
updateHostContainer(workInProgress);
return null;
}
// 5
case HostComponent: {
popHostContext(workInProgress);
// 获取 rootDOM 节点 <div id="root"></div>
const rootContainerInstance = getRootHostContainer();
// 节点的具体的类型 div span ...
const type = workInProgress.type;
// 初始渲染不执行 current = null
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent(
current,
workInProgress,
type,
newProps,
rootContainerInstance,
);
if (enableDeprecatedFlareAPI) {
const prevListeners = current.memoizedProps.DEPRECATED_flareListeners;
const nextListeners = newProps.DEPRECATED_flareListeners;
if (prevListeners !== nextListeners) {
markUpdate(workInProgress);
}
}
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
} else {
if (!newProps) {
invariant(
workInProgress.stateNode !== null,
'We must have new props for new mounts. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
// This can happen when we abort work.
return null;
}
const currentHostContext = getHostContext();
// TODO: Move createInstance to beginWork and keep it on a context
// "stack" as the parent. Then append children as we go in beginWork
// or completeWork depending on whether we want to add them top->down or
// bottom->up. Top->down is faster in IE11.
let wasHydrated = popHydrationState(workInProgress);
// 服务器渲染相关 初始渲染为不执行
// false
if (wasHydrated) {
// TODO: Move this and createInstance step into the beginPhase
// to consolidate.
if (
prepareToHydrateHostInstance(
workInProgress,
rootContainerInstance,
currentHostContext,
)
) {
// If changes to the hydrated node need to be applied at the
// commit-phase we mark this as such.
markUpdate(workInProgress);
}
if (enableDeprecatedFlareAPI) {
const listeners = newProps.DEPRECATED_flareListeners;
if (listeners != null) {
updateDeprecatedEventListeners(
listeners,
workInProgress,
rootContainerInstance,
);
}
}
} else {
// 创建节点实例对象 <div></div> <span></span>
let instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);
/**
* 将所有的子级追加到父级中
* instance 为父级
* workInProgress.child 为子级
*/
appendAllChildren(instance, workInProgress, false, false);
// 为 Fiber 对象添加 stateNode 属性
workInProgress.stateNode = instance;
// 初始渲染不执行
// false
if (enableDeprecatedFlareAPI) {
const listeners = newProps.DEPRECATED_flareListeners;
if (listeners != null) {
updateDeprecatedEventListeners(
listeners,
workInProgress,
rootContainerInstance,
);
}
}
// Certain renderers require commit-time effects for initial mount.
// (eg DOM renderer supports auto-focus for certain elements).
// Make sure such renderers get scheduled for later work.
// 初始渲染不执行
if (
finalizeInitialChildren(
instance,
type,
newProps,
rootContainerInstance,
currentHostContext,
)
) {
markUpdate(workInProgress);
}
}
// 处理 ref DOM 引用
if (workInProgress.ref !== null) {
// If there is a ref on a host node we need to schedule a callback
markRef(workInProgress);
}
}
return null;
}
// 6
case HostText: {
let newText = newProps;
if (current && workInProgress.stateNode != null) {
const oldText = current.memoizedProps;
// If we have an alternate, that means this is an update and we need
// to schedule a side-effect to do the updates.
updateHostText(current, workInProgress, oldText, newText);
} else {
if (typeof newText !== 'string') {
invariant(
workInProgress.stateNode !== null,
'We must have new props for new mounts. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
// This can happen when we abort work.
}
const rootContainerInstance = getRootHostContainer();
const currentHostContext = getHostContext();
let wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
if (prepareToHydrateHostTextInstance(workInProgress)) {
markUpdate(workInProgress);
}
} else {
workInProgress.stateNode = createTextInstance(
newText,
rootContainerInstance,
currentHostContext,
workInProgress,
);
}
}
return null;
}
case SuspenseComponent: {
popSuspenseContext(workInProgress);
const nextState: null | SuspenseState = workInProgress.memoizedState;
if (enableSuspenseServerRenderer) {
if (nextState !== null && nextState.dehydrated !== null) {
if (current === null) {
let wasHydrated = popHydrationState(workInProgress);
invariant(
wasHydrated,
'A dehydrated suspense component was completed without a hydrated node. ' +
'This is probably a bug in React.',
);
prepareToHydrateHostSuspenseInstance(workInProgress);
if (enableSchedulerTracing) {
markSpawnedWork(Never);
}
return null;
} else {
// We should never have been in a hydration state if we didn't have a current.
// However, in some of those paths, we might have reentered a hydration state
// and then we might be inside a hydration state. In that case, we'll need to exit out of it.
resetHydrationState();
if ((workInProgress.effectTag & DidCapture) === NoEffect) {
// This boundary did not suspend so it's now hydrated and unsuspended.
workInProgress.memoizedState = null;
}
// If nothing suspended, we need to schedule an effect to mark this boundary
// as having hydrated so events know that they're free to be invoked.
// It's also a signal to replay events and the suspense callback.
// If something suspended, schedule an effect to attach retry listeners.
// So we might as well always mark this.
workInProgress.effectTag |= Update;
return null;
}
}
}
if ((workInProgress.effectTag & DidCapture) !== NoEffect) {
// Something suspended. Re-render with the fallback children.
workInProgress.expirationTime = renderExpirationTime;
// Do not reset the effect list.
return workInProgress;
}
const nextDidTimeout = nextState !== null;
let prevDidTimeout = false;
if (current === null) {
if (workInProgress.memoizedProps.fallback !== undefined) {
popHydrationState(workInProgress);
}
} else {
const prevState: null | SuspenseState = current.memoizedState;
prevDidTimeout = prevState !== null;
if (!nextDidTimeout && prevState !== null) {
// We just switched from the fallback to the normal children.
// Delete the fallback.
// TODO: Would it be better to store the fallback fragment on
// the stateNode during the begin phase?
const currentFallbackChild: Fiber | null = (current.child: any)
.sibling;
if (currentFallbackChild !== null) {
// Deletions go at the beginning of the return fiber's effect list
const first = workInProgress.firstEffect;
if (first !== null) {
workInProgress.firstEffect = currentFallbackChild;
currentFallbackChild.nextEffect = first;
} else {
workInProgress.firstEffect = workInProgress.lastEffect = currentFallbackChild;
currentFallbackChild.nextEffect = null;
}
currentFallbackChild.effectTag = Deletion;
}
}
}
if (nextDidTimeout && !prevDidTimeout) {
// If this subtreee is running in blocking mode we can suspend,
// otherwise we won't suspend.
// TODO: This will still suspend a synchronous tree if anything
// in the concurrent tree already suspended during this render.
// This is a known bug.
if ((workInProgress.mode & BlockingMode) !== NoMode) {
// TODO: Move this back to throwException because this is too late
// if this is a large tree which is common for initial loads. We
// don't know if we should restart a render or not until we get
// this marker, and this is too late.
// If this render already had a ping or lower pri updates,
// and this is the first time we know we're going to suspend we
// should be able to immediately restart from within throwException.
const hasInvisibleChildContext =
current === null &&
workInProgress.memoizedProps.unstable_avoidThisFallback !== true;
if (
hasInvisibleChildContext ||
hasSuspenseContext(
suspenseStackCursor.current,
(InvisibleParentSuspenseContext: SuspenseContext),
)
) {
// If this was in an invisible tree or a new render, then showing
// this boundary is ok.
renderDidSuspend();
} else {
// Otherwise, we're going to have to hide content so we should
// suspend for longer if possible.
renderDidSuspendDelayIfPossible();
}
}
}
if (supportsPersistence) {
// TODO: Only schedule updates if not prevDidTimeout.
if (nextDidTimeout) {
// If this boundary just timed out, schedule an effect to attach a
// retry listener to the promise. This flag is also used to hide the
// primary children.
workInProgress.effectTag |= Update;
}
}
if (supportsMutation) {
// TODO: Only schedule updates if these values are non equal, i.e. it changed.
if (nextDidTimeout || prevDidTimeout) {
// If this boundary just timed out, schedule an effect to attach a
// retry listener to the promise. This flag is also used to hide the
// primary children. In mutation mode, we also need the flag to
// *unhide* children that were previously hidden, so check if this
// is currently timed out, too.
workInProgress.effectTag |= Update;
}
}
if (
enableSuspenseCallback &&
workInProgress.updateQueue !== null &&
workInProgress.memoizedProps.suspenseCallback != null
) {
// Always notify the callback
workInProgress.effectTag |= Update;
}
return null;
}
case HostPortal:
popHostContainer(workInProgress);
updateHostContainer(workInProgress);
return null;
case ContextProvider:
// Pop provider fiber
popProvider(workInProgress);
return null;
case IncompleteClassComponent: {
// Same as class component case. I put it down here so that the tags are
// sequential to ensure this switch is compiled to a jump table.
const Component = workInProgress.type;
if (isLegacyContextProvider(Component)) {
popLegacyContext(workInProgress);
}
return null;
}
case SuspenseListComponent: {
popSuspenseContext(workInProgress);
const renderState: null | SuspenseListRenderState =
workInProgress.memoizedState;
if (renderState === null) {
// We're running in the default, "independent" mode.
// We don't do anything in this mode.
return null;
}
let didSuspendAlready =
(workInProgress.effectTag & DidCapture) !== NoEffect;
let renderedTail = renderState.rendering;
if (renderedTail === null) {
// We just rendered the head.
if (!didSuspendAlready) {
// This is the first pass. We need to figure out if anything is still
// suspended in the rendered set.
// If new content unsuspended, but there's still some content that
// didn't. Then we need to do a second pass that forces everything
// to keep showing their fallbacks.
// We might be suspended if something in this render pass suspended, or
// something in the previous committed pass suspended. Otherwise,
// there's no chance so we can skip the expensive call to
// findFirstSuspended.
let cannotBeSuspended =
renderHasNotSuspendedYet() &&
(current === null || (current.effectTag & DidCapture) === NoEffect);
if (!cannotBeSuspended) {
let row = workInProgress.child;
while (row !== null) {
let suspended = findFirstSuspended(row);
if (suspended !== null) {
didSuspendAlready = true;
workInProgress.effectTag |= DidCapture;
cutOffTailIfNeeded(renderState, false);
// If this is a newly suspended tree, it might not get committed as
// part of the second pass. In that case nothing will subscribe to
// its thennables. Instead, we'll transfer its thennables to the
// SuspenseList so that it can retry if they resolve.
// There might be multiple of these in the list but since we're
// going to wait for all of them anyway, it doesn't really matter
// which ones gets to ping. In theory we could get clever and keep
// track of how many dependencies remain but it gets tricky because
// in the meantime, we can add/remove/change items and dependencies.
// We might bail out of the loop before finding any but that
// doesn't matter since that means that the other boundaries that
// we did find already has their listeners attached.
let newThennables = suspended.updateQueue;
if (newThennables !== null) {
workInProgress.updateQueue = newThennables;
workInProgress.effectTag |= Update;
}
// Rerender the whole list, but this time, we'll force fallbacks
// to stay in place.
// Reset the effect list before doing the second pass since that's now invalid.
if (renderState.lastEffect === null) {
workInProgress.firstEffect = null;
}
workInProgress.lastEffect = renderState.lastEffect;
// Reset the child fibers to their original state.
resetChildFibers(workInProgress, renderExpirationTime);
// Set up the Suspense Context to force suspense and immediately
// rerender the children.
pushSuspenseContext(
workInProgress,
setShallowSuspenseContext(
suspenseStackCursor.current,
ForceSuspenseFallback,
),
);
return workInProgress.child;
}
row = row.sibling;
}
}
} else {
cutOffTailIfNeeded(renderState, false);
}
// Next we're going to render the tail.
} else {
// Append the rendered row to the child list.
if (!didSuspendAlready) {
let suspended = findFirstSuspended(renderedTail);
if (suspended !== null) {
workInProgress.effectTag |= DidCapture;
didSuspendAlready = true;
// Ensure we transfer the update queue to the parent so that it doesn't
// get lost if this row ends up dropped during a second pass.
let newThennables = suspended.updateQueue;
if (newThennables !== null) {
workInProgress.updateQueue = newThennables;
workInProgress.effectTag |= Update;
}
cutOffTailIfNeeded(renderState, true);
// This might have been modified.
if (
renderState.tail === null &&
renderState.tailMode === 'hidden' &&
!renderedTail.alternate
) {
// We need to delete the row we just rendered.
// Reset the effect list to what it was before we rendered this
// child. The nested children have already appended themselves.
let lastEffect = (workInProgress.lastEffect =
renderState.lastEffect);
// Remove any effects that were appended after this point.
if (lastEffect !== null) {
lastEffect.nextEffect = null;
}
// We're done.
return null;
}
} else if (
// The time it took to render last row is greater than time until
// the expiration.
now() * 2 - renderState.renderingStartTime >
renderState.tailExpiration &&
renderExpirationTime > Never
) {
// We have now passed our CPU deadline and we'll just give up further
// attempts to render the main content and only render fallbacks.
// The assumption is that this is usually faster.
workInProgress.effectTag |= DidCapture;
didSuspendAlready = true;
cutOffTailIfNeeded(renderState, false);
// Since nothing actually suspended, there will nothing to ping this
// to get it started back up to attempt the next item. If we can show
// them, then they really have the same priority as this render.
// So we'll pick it back up the very next render pass once we've had
// an opportunity to yield for paint.
const nextPriority = renderExpirationTime - 1;
workInProgress.expirationTime = workInProgress.childExpirationTime = nextPriority;
if (enableSchedulerTracing) {
markSpawnedWork(nextPriority);
}
}
}
if (renderState.isBackwards) {
// The effect list of the backwards tail will have been added
// to the end. This breaks the guarantee that life-cycles fire in
// sibling order but that isn't a strong guarantee promised by React.
// Especially since these might also just pop in during future commits.
// Append to the beginning of the list.
renderedTail.sibling = workInProgress.child;
workInProgress.child = renderedTail;
} else {
let previousSibling = renderState.last;
if (previousSibling !== null) {
previousSibling.sibling = renderedTail;
} else {
workInProgress.child = renderedTail;
}
renderState.last = renderedTail;
}
}
if (renderState.tail !== null) {
// We still have tail rows to render.
if (renderState.tailExpiration === 0) {
// Heuristic for how long we're willing to spend rendering rows
// until we just give up and show what we have so far.
const TAIL_EXPIRATION_TIMEOUT_MS = 500;
renderState.tailExpiration = now() + TAIL_EXPIRATION_TIMEOUT_MS;
// TODO: This is meant to mimic the train model or JND but this
// is a per component value. It should really be since the start
// of the total render or last commit. Consider using something like
// globalMostRecentFallbackTime. That doesn't account for being
// suspended for part of the time or when it's a new render.
// It should probably use a global start time value instead.
}
// Pop a row.
let next = renderState.tail;
renderState.rendering = next;
renderState.tail = next.sibling;
renderState.lastEffect = workInProgress.lastEffect;
renderState.renderingStartTime = now();
next.sibling = null;
// Restore the context.
// TODO: We can probably just avoid popping it instead and only
// setting it the first time we go from not suspended to suspended.
let suspenseContext = suspenseStackCursor.current;
if (didSuspendAlready) {
suspenseContext = setShallowSuspenseContext(
suspenseContext,
ForceSuspenseFallback,
);
} else {
suspenseContext = setDefaultShallowSuspenseContext(suspenseContext);
}
pushSuspenseContext(workInProgress, suspenseContext);
// Do a pass over the next row.
return next;
}
return null;
}
case FundamentalComponent: {
if (enableFundamentalAPI) {
const fundamentalImpl = workInProgress.type.impl;
let fundamentalInstance: ReactFundamentalComponentInstance<
any,
any,
> | null = workInProgress.stateNode;
if (fundamentalInstance === null) {
const getInitialState = fundamentalImpl.getInitialState;
let fundamentalState;
if (getInitialState !== undefined) {
fundamentalState = getInitialState(newProps);
}
fundamentalInstance = workInProgress.stateNode = createFundamentalStateInstance(
workInProgress,
newProps,
fundamentalImpl,
fundamentalState || {},
);
const instance = ((getFundamentalComponentInstance(
fundamentalInstance,
): any): Instance);
fundamentalInstance.instance = instance;
if (fundamentalImpl.reconcileChildren === false) {
return null;
}
appendAllChildren(instance, workInProgress, false, false);
mountFundamentalComponent(fundamentalInstance);
} else {
// We fire update in commit phase
const prevProps = fundamentalInstance.props;
fundamentalInstance.prevProps = prevProps;
fundamentalInstance.props = newProps;
fundamentalInstance.currentFiber = workInProgress;
if (supportsPersistence) {
const instance = cloneFundamentalInstance(fundamentalInstance);
fundamentalInstance.instance = instance;
appendAllChildren(instance, workInProgress, false, false);
}
const shouldUpdate = shouldUpdateFundamentalComponent(
fundamentalInstance,
);
if (shouldUpdate) {
markUpdate(workInProgress);
}
}
return null;
}
break;
}
case ScopeComponent: {
if (enableScopeAPI) {
if (current === null) {
const type = workInProgress.type;
const scopeInstance: ReactScopeInstance = {
fiber: workInProgress,
methods: null,
};
workInProgress.stateNode = scopeInstance;
scopeInstance.methods = createScopeMethods(type, scopeInstance);
if (enableDeprecatedFlareAPI) {
const listeners = newProps.DEPRECATED_flareListeners;
if (listeners != null) {
const rootContainerInstance = getRootHostContainer();
updateDeprecatedEventListeners(
listeners,
workInProgress,
rootContainerInstance,
);
}
}
if (workInProgress.ref !== null) {
markRef(workInProgress);
markUpdate(workInProgress);
}
} else {
if (enableDeprecatedFlareAPI) {
const prevListeners =
current.memoizedProps.DEPRECATED_flareListeners;
const nextListeners = newProps.DEPRECATED_flareListeners;
if (
prevListeners !== nextListeners ||
workInProgress.ref !== null
) {
markUpdate(workInProgress);
}
} else {
if (workInProgress.ref !== null) {
markUpdate(workInProgress);
}
}
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
}
return null;
}
break;
}
case Block:
if (enableBlocksAPI) {
return null;
}
break;
}
invariant(
false,
'Unknown unit of work tag (%s). This error is likely caused by a bug in ' +
'React. Please file an issue.',
workInProgress.tag,
);
}
// 创建节点实例对象 <div></div> <span></span>
export function createInstance(
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
): Instance {
let parentNamespace: string;
if (__DEV__) {
// TODO: take namespace into account when validating.
const hostContextDev = ((hostContext: any): HostContextDev);
validateDOMNesting(type, null, hostContextDev.ancestorInfo);
if (
typeof props.children === 'string' ||
typeof props.children === 'number'
) {
const string = '' + props.children;
const ownAncestorInfo = updatedAncestorInfo(
hostContextDev.ancestorInfo,
type,
);
validateDOMNesting(null, string, ownAncestorInfo);
}
parentNamespace = hostContextDev.namespace;
} else {
parentNamespace = ((hostContext: any): HostContextProd);
}
const domElement: Instance = createElement(
type,
props,
rootContainerInstance,
parentNamespace,
);
precacheFiberNode(internalInstanceHandle, domElement);
updateFiberProps(domElement, props);
return domElement;
}
// 创建 真实 DOM 对象
export function createElement(
type: string,
props: Object,
rootContainerElement: Element | Document,
parentNamespace: string,
): Element {
let isCustomComponentTag;
// We create tags in the namespace of their parent container, except HTML
// tags get no namespace.
const ownerDocument: Document = getOwnerDocumentFromRootContainer(
rootContainerElement,
);
let domElement: Element;
let namespaceURI = parentNamespace;
if (namespaceURI === HTML_NAMESPACE) {
namespaceURI = getIntrinsicNamespace(type);
}
if (namespaceURI === HTML_NAMESPACE) {
if (__DEV__) {
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()) {
console.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
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>'; // eslint-disable-line
// This is guaranteed to yield a script element.
const firstChild = ((div.firstChild: any): HTMLScriptElement);
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 {
// Separate else branch instead of using `props.is || undefined` above because of a Firefox bug.
// See discussion in https://github.com/facebook/react/pull/6896
// and discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1276240
domElement = ownerDocument.createElement(type);
// Normally attributes are assigned in `setInitialDOMProperties`, however the `multiple` and `size`
// attributes on `select`s needs to be added before `option`s are inserted.
// This prevents:
// - a bug where the `select` does not scroll to the correct option because singular
// `select` elements automatically pick the first item #13222
// - a bug where the `select` set the first item as selected despite the `size` attribute #14239
// See https://github.com/facebook/react/issues/13222
// and https://github.com/facebook/react/issues/14239
if (type === 'select') {
const node = ((domElement: any): HTMLSelectElement);
if (props.multiple) {
node.multiple = true;
} else if (props.size) {
// Setting a size greater than 1 causes a select to behave like `multiple=true`, where
// it is possible that no option is selected.
//
// This is only necessary when a select in "single selection mode".
node.size = props.size;
}
}
}
} else {
domElement = ownerDocument.createElementNS(namespaceURI, type);
}
if (__DEV__) {
if (namespaceURI === HTML_NAMESPACE) {
if (
!isCustomComponentTag &&
Object.prototype.toString.call(domElement) ===
'[object HTMLUnknownElement]' &&
!Object.prototype.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,
);
}
}
}
return domElement;
}
// 将所有子级追到到父级中
appendAllChildren = function (
parent: Instance,
workInProgress: Fiber,
needsVisibilityToggle: boolean,
isHidden: boolean,
) {
// 获取子级
let node = workInProgress.child;
// 如果子级不为空 执行循环
while (node !== null) {
// 如果 node 是普通 ReactElement 或者为文本
if (node.tag === HostComponent || node.tag === HostText) {
// 将子级追加到父级中
appendInitialChild(parent, node.stateNode);
} else if (enableFundamentalAPI && node.tag === FundamentalComponent) {
appendInitialChild(parent, node.stateNode.instance);
} else if (node.tag === HostPortal) {
// If we have a portal child, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
// the portal directly.
} else if (node.child !== null) {
// 如果 node 不是普通 ReactElement 又不是文本
// 将 node 视为组件, 组件本身不能转换为真实 DOM 元素
// 获取到组件的第一个子元素, 继续执行循环
node.child.return = node;
node = node.child;
continue;
}
// 如果 node 和 workInProgress 是同一个节点
// 说明 node 已经退回到父级 终止循环
// 说明此时所有子级都已经追加到父级中了
if (node === workInProgress) {
return;
}
// 处理子级节点的兄弟节点
while (node.sibling === null) {
// 如果节点没有父级或者节点的父级是自己, 退出循环
// 说明此时所有子级都已经追加到父级中了
if (node.return === null || node.return === workInProgress) {
return;
}
// 更新 node
node = node.return;
}
// 更新父级 方便回退
node.sibling.return = node.return;
// 将 node 更新为下一个兄弟节点
node = node.sibling;
}
};
// 将子级追加到父级中
export function appendInitialChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
parentInstance.appendChild(child);
}
15. 结束 render 阶段,进入 commit 阶段(具体请查看此内容,因)
// 结束 render 阶段
// 进入 commit 阶段
function finishSyncRender(root) {
// 销毁 workInProgress Fiber 树
// 因为待提交 Fiber 对象已经被存储在了 root.finishedWork 中
workInProgressRoot = null;
// 进入 commit 阶段
commitRoot(root);
}
// 进入 commit 阶段
function commitRoot(root) {
// 获取任务优先级 97 => 普通优先级
const renderPriorityLevel = getCurrentPriorityLevel();
// 使用最高优先级执行当前任务, 因为 commit 阶段不可以被打断
// ImmediatePriority, 优先级为 99, 最高优先级
runWithPriority(
ImmediatePriority,
commitRootImpl.bind(null, root, renderPriorityLevel),
);
return null;
}
function commitRootImpl(root, renderPriorityLevel) {
do {
// 触发useEffect回调与其他同步任务
// 由于这些任务可能触发新的渲染
// 所以这里要一直遍历执行直到没有任务
flushPassiveEffects();
} while (rootWithPendingPassiveEffects !== null);
// 开发环境执行 忽略
flushRenderPhaseStrictModeWarningsInDEV();
invariant(
(executionContext & (RenderContext | CommitContext)) === NoContext,
'Should not already be working.',
);
// 获取待提交 Fiber 对象 rootFiber
const finishedWork = root.finishedWork;
// 1073741823
const expirationTime = root.finishedExpirationTime;
// 如果没有任务要执行
if (finishedWork === null) {
// 阻止程序继续向下执行
return null;
}
// 重置为默认值
root.finishedWork = null;
root.finishedExpirationTime = NoWork;
invariant(
finishedWork !== root.current,
'Cannot commit the same tree as before. This error is likely caused by ' +
'a bug in React. Please file an issue.',
);
// commitRoot 是最后阶段, 不会再被异步调用了
// 所以清除 callback 相关的属性
root.callbackNode = null;
root.callbackExpirationTime = NoWork;
root.callbackPriority = NoPriority;
root.nextKnownPendingLevel = NoWork;
startCommitTimer();
// Update the first and last pending times on this root. The new first
// pending time is whatever is left on the root fiber.
const remainingExpirationTimeBeforeCommit = getRemainingExpirationTime(
finishedWork,
);
// 重置优先级相关变量
markRootFinishedAtTime(
root,
expirationTime,
remainingExpirationTimeBeforeCommit,
);
// false
if (root === workInProgressRoot) {
// We can reset these now that they are finished.
workInProgressRoot = null;
workInProgress = null;
renderExpirationTime = NoWork;
} else {
// 这表明我们处理的最后一个根与我们现在提交的根不同
// 最常见的情况是在挂起的根超时时发生
}
// 将 effectList 赋值给 firstEffect
// 由于每个 Fiber 的 effectList 只包含他的子孙节点
// 所以根节点如果有 effectTag 则不会被包含进来
// 所以这里将有 effectTag 的根节点插入到 effectList 尾部
// 这样才能保证有 effect 的 fiber 都在 effectList 中
let firstEffect;
// finishedWork.effectTag => 0
// PerformedWork => 1
// false
if (finishedWork.effectTag > PerformedWork) {
if (finishedWork.lastEffect !== null) {
finishedWork.lastEffect.nextEffect = finishedWork;
firstEffect = finishedWork.firstEffect;
} else {
firstEffect = finishedWork;
}
} else {
// 根节点没有 effectTag
// 获取要执行 DOM 操作的副作用列表
firstEffect = finishedWork.firstEffect;
}
// 以上代码为 commit 之前所做的准备工作
// firstEffect 会在 commit 的三个子阶段会用到
// true
if (firstEffect !== null) {
// 8
const prevExecutionContext = executionContext;
// 40
executionContext |= CommitContext;
const prevInteractions = pushInteractions(root);
// Reset this to null before calling lifecycles
ReactCurrentOwner.current = null;
// The commit phase is broken into several sub-phases. We do a separate pass
// of the effect list for each phase: all mutation effects come before all
// layout effects, and so on.
// The first phase a "before mutation" phase. We use this phase to read the
// state of the host tree right before we mutate it. This is where
// getSnapshotBeforeUpdate is called.
startCommitSnapshotEffectsTimer();
prepareForCommit(root.containerInfo);
nextEffect = firstEffect;
// commit 第一个子阶段
// 处理类组件的 getSnapShotBeforeUpdate 生命周期函数
do {
if (__DEV__) {
invokeGuardedCallback(null, commitBeforeMutationEffects, null);
if (hasCaughtError()) {
invariant(nextEffect !== null, 'Should be working on an effect.');
const error = clearCaughtError();
captureCommitPhaseError(nextEffect, error);
nextEffect = nextEffect.nextEffect;
}
} else {
try {
commitBeforeMutationEffects();
} catch (error) {
invariant(nextEffect !== null, 'Should be working on an effect.');
captureCommitPhaseError(nextEffect, error);
nextEffect = nextEffect.nextEffect;
}
}
} while (nextEffect !== null);
stopCommitSnapshotEffectsTimer();
if (enableProfilerTimer) {
// Mark the current commit time to be shared by all Profilers in this
// batch. This enables them to be grouped later.
recordCommitTime();
}
// The next phase is the mutation phase, where we mutate the host tree.
startCommitHostEffectsTimer();
// commit 第二个子阶段
nextEffect = firstEffect;
do {
if (__DEV__) {
invokeGuardedCallback(
null,
commitMutationEffects,
null,
root,
renderPriorityLevel,
);
if (hasCaughtError()) {
invariant(nextEffect !== null, 'Should be working on an effect.');
const error = clearCaughtError();
captureCommitPhaseError(nextEffect, error);
nextEffect = nextEffect.nextEffect;
}
} else {
try {
commitMutationEffects(root, renderPriorityLevel);
} catch (error) {
invariant(nextEffect !== null, 'Should be working on an effect.');
captureCommitPhaseError(nextEffect, error);
nextEffect = nextEffect.nextEffect;
}
}
} while (nextEffect !== null);
stopCommitHostEffectsTimer();
resetAfterCommit(root.containerInfo);
// The work-in-progress tree is now the current tree. This must come after
// the mutation phase, so that the previous tree is still current during
// componentWillUnmount, but before the layout phase, so that the finished
// work is current during componentDidMount/Update.
root.current = finishedWork;
// The next phase is the layout phase, where we call effects that read
// the host tree after it's been mutated. The idiomatic use case for this is
// layout, but class component lifecycles also fire here for legacy reasons.
startCommitLifeCyclesTimer();
// commit 第三个子阶段
nextEffect = firstEffect;
do {
if (__DEV__) {
invokeGuardedCallback(
null,
commitLayoutEffects,
null,
root,
expirationTime,
);
if (hasCaughtError()) {
invariant(nextEffect !== null, 'Should be working on an effect.');
const error = clearCaughtError();
captureCommitPhaseError(nextEffect, error);
nextEffect = nextEffect.nextEffect;
}
} else {
try {
commitLayoutEffects(root, expirationTime);
} catch (error) {
invariant(nextEffect !== null, 'Should be working on an effect.');
captureCommitPhaseError(nextEffect, error);
nextEffect = nextEffect.nextEffect;
}
}
} while (nextEffect !== null);
stopCommitLifeCyclesTimer();
// 重置 nextEffect
nextEffect = null;
// Tell Scheduler to yield at the end of the frame, so the browser has an
// opportunity to paint.
requestPaint();
if (enableSchedulerTracing) {
popInteractions(((prevInteractions: any): Set<Interaction>));
}
executionContext = prevExecutionContext;
} else {
// No effects.
root.current = finishedWork;
// Measure these anyway so the flamegraph explicitly shows that there were
// no effects.
// TODO: Maybe there's a better way to report this.
startCommitSnapshotEffectsTimer();
stopCommitSnapshotEffectsTimer();
if (enableProfilerTimer) {
recordCommitTime();
}
startCommitHostEffectsTimer();
stopCommitHostEffectsTimer();
startCommitLifeCyclesTimer();
stopCommitLifeCyclesTimer();
}
stopCommitTimer();
const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;
if (rootDoesHavePassiveEffects) {
// This commit has passive effects. Stash a reference to them. But don't
// schedule a callback until after flushing layout work.
rootDoesHavePassiveEffects = false;
rootWithPendingPassiveEffects = root;
pendingPassiveEffectsExpirationTime = expirationTime;
pendingPassiveEffectsRenderPriority = renderPriorityLevel;
} else {
// We are done with the effect chain at this point so let's clear the
// nextEffect pointers to assist with GC. If we have passive effects, we'll
// clear this in flushPassiveEffects.
nextEffect = firstEffect;
while (nextEffect !== null) {
const nextNextEffect = nextEffect.nextEffect;
nextEffect.nextEffect = null;
nextEffect = nextNextEffect;
}
}
// Check if there's remaining work on this root
const remainingExpirationTime = root.firstPendingTime;
if (remainingExpirationTime !== NoWork) {
if (enableSchedulerTracing) {
if (spawnedWorkDuringRender !== null) {
const expirationTimes = spawnedWorkDuringRender;
spawnedWorkDuringRender = null;
for (let i = 0; i < expirationTimes.length; i++) {
scheduleInteractions(
root,
expirationTimes[i],
root.memoizedInteractions,
);
}
}
schedulePendingInteractions(root, remainingExpirationTime);
}
} else {
// If there's no remaining work, we can clear the set of already failed
// error boundaries.
legacyErrorBoundariesThatAlreadyFailed = null;
}
if (enableSchedulerTracing) {
if (!rootDidHavePassiveEffects) {
// If there are no passive effects, then we can complete the pending interactions.
// Otherwise, we'll wait until after the passive effects are flushed.
// Wait to do this until after remaining work has been scheduled,
// so that we don't prematurely signal complete for interactions when there's e.g. hidden work.
finishPendingInteractions(root, expirationTime);
}
}
if (remainingExpirationTime === Sync) {
// Count the number of times the root synchronously re-renders without
// finishing. If there are too many, it indicates an infinite update loop.
if (root === rootWithNestedUpdates) {
nestedUpdateCount++;
} else {
nestedUpdateCount = 0;
rootWithNestedUpdates = root;
}
} else {
nestedUpdateCount = 0;
}
onCommitRoot(finishedWork.stateNode, expirationTime);
// Always call this before exiting `commitRoot`, to ensure that any
// additional work on this root is scheduled.
ensureRootIsScheduled(root);
if (hasUncaughtError) {
hasUncaughtError = false;
const error = firstUncaughtError;
firstUncaughtError = null;
throw error;
}
if ((executionContext & LegacyUnbatchedContext) !== NoContext) {
// This is a legacy edge case. We just committed the initial mount of
// a ReactDOM.render-ed root inside of batchedUpdates. The commit fired
// synchronously, but layout updates should be deferred until the end
// of the batch.
return null;
}
// If layout work was scheduled, flush it now.
flushSyncCallbackQueue();
return null;
}


浙公网安备 33010602011771号