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; }