学习资料:https://react.iamkasong.com/process/reconciler.html#%E9%80%92-%E9%98%B6%E6%AE%B5
render阶段过程:采用深度优先遍历的方式,依次执行beginWork和completeWork
第一次进入beginWork,存在current,tag为3
3表示是根节点
下一次执行,进入function App的beginWork,对于双缓存机制,再一次进入current为null,
存在workInProgress
测试demo
再执行一次,下一个进入beginWork的是div
再执行一次,下一个进入beginWork的是header
再执行一次,下一个进入beginWork的是img
由于img没有子节点,执行completeWork
img的completeWork执行完后,会执行img的兄弟节点P的BeginWork
此时p有3个子节点,Edit、<code>src/App.js</code>、and save to reload,下一次执行,进入p的子节点Edit
由于Edit没有子节点,会进入Edit的completeWork
Edit的completeWork执行完之后,会进入Edit的兄弟节点的beginWork
在执行一次,进入了code的completeWork,因为React对只有一个文本节点的子节点做了优化,这个子节点不会产生自己的Fiber节点。因此下一次执行了code的completeWork。
接下来,code寻找自己的兄弟节点文本节点and save to reload的beginWork,再继续执行文本节点的completeWork。
下一次执行,由于文本节点没有兄弟节点,会执行p的completeWork。
下一次执行,a节点的beginWork。
下一次执行,由于a节点的子节点是文本节点,执行completeWork。
下一次执行,由于a节点没有兄弟节点,执行header的completeWork。
下一次执行,由于header节点没有兄弟节点,执行#app div的completeWork。
下一次执行,由于#app div节点没有兄弟节点,执行方法App的completeWork。
再一次执行,App的根节点,即tag为3的根节点的completeWork。
在接下来,rander阶段完成,进入commit阶段,渲染页面。
BeginWork的作用 —传入当前Fiber节点,创建子Fiber节点,并将者两个Fiber节点连接起来。当遍历到叶子节点(即没有子节点的组件)时,就会进入”归”阶段。
function beginWork(current$$1, workInProgress, renderExpirationTime) { var updateExpirationTime = workInProgress.expirationTime; ... // 根据不同的tag,进入不同的case workInProgress.expirationTime = NoWork; switch (workInProgress.tag) { case IndeterminateComponent: ... case LazyComponent: ... case FunctionComponent: ... case ClassComponent: ... case HostRoot: return updateHostRoot(current$$1, workInProgress, renderExpirationTime); case HostComponent: return updateHostComponent(current$$1, workInProgress, renderExpirationTime); case HostText: return updateHostText(current$$1, workInProgress); case SuspenseComponent: return updateSuspenseComponent(current$$1, workInProgress, renderExpirationTime); case HostPortal: return updatePortalComponent(current$$1, workInProgress, renderExpirationTime); case ForwardRef: ... } }
updateHostComponent
以div举例,div是HostComponent,进入updateHostComponent
function updateHostComponent(current$$1, workInProgress, renderExpirationTime) { pushHostContext(workInProgress); if (current$$1 === null) { tryToClaimNextHydratableInstance(workInProgress); } //先赋值 var type = workInProgress.type; var nextProps = workInProgress.pendingProps; var prevProps = current$$1 !== null ? current$$1.memoizedProps : null; var nextChildren = nextProps.children; //检查当前的fiber节点,是否只有一个唯一的文本子节点,比如a标签, //如果是唯一的文本子节点,react不会创建fiber节点(优化点) var isDirectTextChild = shouldSetTextContent(type, nextProps); if (isDirectTextChild) { nextChildren = null; } else if (prevProps !== null && shouldSetTextContent(type, prevProps)) { workInProgress.effectTag |= ContentReset; } markRef(current$$1, workInProgress); // Check the host config to see if the children are offscreen/hidden. if (renderExpirationTime !== Never && workInProgress.mode & ConcurrentMode && shouldDeprioritizeSubtree(type, nextProps)) { // Schedule this fiber to re-render at offscreen priority. Then bailout. workInProgress.expirationTime = workInProgress.childExpirationTime = Never; return null; } //进入reconcileChildren方法 reconcileChildren(current$$1, workInProgress, nextChildren, renderExpirationTime); return workInProgress.child;}
reconcileChildren
再执行reconcileChildren之前,当前的workInProgress.child是null

function reconcileChildren(current$$1, workInProgress, nextChildren, renderExpirationTime) { if (current$$1 === null) { /*如果这是一个尚未渲染的全新组件,我们 不会通过应用最小的副作用来更新其子集。相反 在渲染之前,我们会将它们全部添加到子级中。这意味着 我们可以通过不跟踪副作用来优化这种对账传递。*/ workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderExpirationTime); } else { /* 如果当前子项与正在进行的工作相同,则意味着 我们还没有开始对这些孩子进行任何研究。因此,我们使用 克隆算法以创建所有当前子级的副本。 如果我们已经有任何进展,那么在这一点上是无效的,所以 让我们把它扔掉。*/ workInProgress.child = reconcileChildFibers(workInProgress, current$$1.child, nextChildren, renderExpirationTime); }}
mountChildFibers与mountChildFiber都是ChildReconciler方法根据不同的Boolean值处理的。
export const reconcileChildFibers = ChildReconciler(true);export const mountChildFibers = ChildReconciler(false);
查看Placement的值,
ReactFiberFlags.js保存了所有的副作用,render阶段不会进行具体dom操作,具体dom操作是在commit阶段执行的,render阶段为需要执行操作的fiber节点打上标记。
/*** Copyright (c) Facebook, Inc. and its affiliates.** This source code is licensed under the MIT license found in the* LICENSE file in the root directory of this source tree.** @flow*/import {enableCreateEventHandleAPI} from 'shared/ReactFeatureFlags';export type Flags = number;// Don't change these two values. They're used by React Dev Tools.export const NoFlags = /* */ 0b00000000000000000000000000;export const PerformedWork = /* */ 0b00000000000000000000000001;// You can change the rest (and add more).export const Placement = /* */ 0b00000000000000000000000010;export const Update = /* */ 0b00000000000000000000000100;export const Deletion = /* */ 0b00000000000000000000001000;export const ChildDeletion = /* */ 0b00000000000000000000010000;export const ContentReset = /* */ 0b00000000000000000000100000;export const Callback = /* */ 0b00000000000000000001000000;export const DidCapture = /* */ 0b00000000000000000010000000;export const ForceClientRender = /* */ 0b00000000000000000100000000;export const Ref = /* */ 0b00000000000000001000000000;export const Snapshot = /* */ 0b00000000000000010000000000;export const Passive = /* */ 0b00000000000000100000000000;export const Hydrating = /* */ 0b00000000000001000000000000;export const Visibility = /* */ 0b00000000000010000000000000;export const StoreConsistency = /* */ 0b00000000000100000000000000;export const LifecycleEffectMask = Passive | Update | Callback | Ref | Snapshot | StoreConsistency;// Union of all commit flags (flags with the lifetime of a particular commit)export const HostEffectMask = /* */ 0b00000000000111111111111111;// These are not really side effects, but we still reuse this field.export const Incomplete = /* */ 0b00000000001000000000000000;export const ShouldCapture = /* */ 0b00000000010000000000000000;export const ForceUpdateForLegacySuspense = /* */ 0b00000000100000000000000000;export const DidPropagateContext = /* */ 0b00000001000000000000000000;export const NeedsPropagation = /* */ 0b00000010000000000000000000;export const Forked = /* */ 0b00000100000000000000000000;// Static tags describe aspects of a fiber that are not specific to a render,// e.g. a fiber uses a passive effect (even if there are no updates on this particular render).// This enables us to defer more work in the unmount case,// since we can defer traversing the tree during layout to look for Passive effects,// and instead rely on the static flag as a signal that there may be cleanup work.export const RefStatic = /* */ 0b00001000000000000000000000;export const LayoutStatic = /* */ 0b00010000000000000000000000;export const PassiveStatic = /* */ 0b00100000000000000000000000;// These flags allow us to traverse to fibers that have effects on mount// without traversing the entire tree after every commit for// double invokingexport const MountLayoutDev = /* */ 0b01000000000000000000000000;export const MountPassiveDev = /* */ 0b10000000000000000000000000;// Groups of flags that are used in the commit phase to skip over trees that// don't contain effects, by checking subtreeFlags.export const BeforeMutationMask = // TODO: Remove Update flag from before mutation phase by re-landing Visibility // flag logic (see #20043) Update | Snapshot | (enableCreateEventHandleAPI ? // createEventHandle needs to visit deleted and hidden trees to // fire beforeblur // TODO: Only need to visit Deletions during BeforeMutation phase if an // element is focused. ChildDeletion | Visibility : 0);export const MutationMask = Placement | Update | ChildDeletion | ContentReset | Ref | Hydrating | Visibility;export const LayoutMask = Update | Callback | Ref | Visibility;// TODO: Split into PassiveMountMask and PassiveUnmountMaskexport const PassiveMask = Passive | ChildDeletion;// Union of tags that don't get reset on clones.// This allows certain concepts to persist without recalculating them,// e.g. whether a subtree contains passive effects or portals.export const StaticMask = LayoutStatic | PassiveStatic | RefStatic;
reconcileChildFibers
进入reconcileChildFibers,
function reconcileChildFibers(returnFiber, currentFirstChild, newChild, expirationTime) { var isUnkeyedTopLevelFragment = typeof newChild === "object" && newChild !== null && newChild.type === REACT_FRAGMENT_TYPE && newChild.key === null if (isUnkeyedTopLevelFragment) { newChild = newChild.props.children } // 判断child类型,对不同类型,进入不同处理逻辑 var isObject = typeof newChild === "object" && newChild !== null if (isObject) { switch (newChild.$$typeof) { //如果值是REACT_ELEMENT_TYPE,被当作一个单一的react_elment来处理 case REACT_ELEMENT_TYPE: return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild, expirationTime)) case REACT_PORTAL_TYPE: return placeSingleChild(reconcileSinglePortal(returnFiber, currentFirstChild, newChild, expirationTime)) } } //如果当前的newChild是string或者number类型,会当作一个文本节点处理 if (typeof newChild === "string" || typeof newChild === "number") { return placeSingleChild(reconcileSingleTextNode(returnFiber, currentFirstChild, "" + newChild, expirationTime)) } //如果当前的newChild是数组,会当作一个数组处理。数组情况,例如header中有img,p,a三个子节点,即会被当作数组处理。 if (isArray(newChild)) { return reconcileChildrenArray(returnFiber, currentFirstChild, newChild, expirationTime) } ... }
reconcileSingleElement
进入reconcileSingleElement,

function reconcileSingleElement(returnFiber, currentFirstChild, element, expirationTime) { var key = element.key var 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 === Fragment ? element.type === REACT_FRAGMENT_TYPE : child.elementType === element.type) { deleteRemainingChildren(returnFiber, child.sibling) var existing = useFiber(child, element.type === REACT_FRAGMENT_TYPE ? element.props.children : element.props, expirationTime) existing.ref = coerceRef(returnFiber, child, element) existing.return = returnFiber { existing._debugSource = element._source existing._debugOwner = element._owner } return existing } else { deleteRemainingChildren(returnFiber, child) break } } else { deleteChild(returnFiber, child) } child = child.sibling } if (element.type === REACT_FRAGMENT_TYPE) { var created = createFiberFromFragment(element.props.children, returnFiber.mode, expirationTime, element.key) created.return = returnFiber return created } else { //最终进入这里 var _created4 = createFiberFromElement(element, returnFiber.mode, expirationTime) _created4.ref = coerceRef(returnFiber, currentFirstChild, element) _created4.return = returnFiber return _created4 } }
createFiberFromElement
进入createFiberFromElement
export function createFiberFromElement( element: ReactElement, mode: TypeOfMode, lanes: Lanes,): Fiber { let owner = null; ... const type = element.type; const key = element.key; const pendingProps = element.props; const fiber = createFiberFromTypeAndProps( type, key, pendingProps, owner, mode, lanes, ); ... return fiber;}
createFiberFromTypeAndProps
createFiberFromTypeAndProps
判断type类型,当前type类型为string,于是fiberTag = HostComponent;
export function createFiberFromTypeAndProps( type: any, // React$ElementType key: null | string, pendingProps: any, owner: null | Fiber, mode: TypeOfMode, lanes: Lanes,): Fiber { let fiberTag = IndeterminateComponent; //判断type类型 let resolvedType = type; if (typeof type === 'function') { if (shouldConstruct(type)) { fiberTag = ClassComponent; } } else if (typeof type === 'string') { fiberTag = HostComponent; } else { ... } const fiber = createFiber(fiberTag, pendingProps, key, mode); fiber.elementType = type; fiber.type = resolvedType; fiber.lanes = lanes; return fiber;}
createFiber
进入createFiber,创建对应的fiber节点
const createFiber = function( tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode,): Fiber { //创建Fiber节点 return new FiberNode(tag, pendingProps, key, mode);};
FiberNode
进入FiberNode,有很多的属性
function FiberNode( tag: WorkTag, pendingProps: mixed, key: null | string, mode: TypeOfMode,) { // Instance this.tag = tag; this.key = key; this.elementType = null; this.type = null; this.stateNode = null; // Fiber this.return = null; this.child = null; this.sibling = null; this.index = 0; this.ref = null; this.pendingProps = pendingProps; this.memoizedProps = null; this.updateQueue = null; this.memoizedState = null; this.dependencies = null; this.mode = mode; // Effects this.flags = NoFlags; this.subtreeFlags = NoFlags; this.deletions = null; this.lanes = NoLanes; this.childLanes = NoLanes; this.alternate = null; if (enableProfilerTimer) { this.actualDuration = Number.NaN; this.actualStartTime = Number.NaN; this.selfBaseDuration = Number.NaN; this.treeBaseDuration = Number.NaN; this.actualDuration = 0; this.actualStartTime = -1; this.selfBaseDuration = 0; this.treeBaseDuration = 0; } ...}
总结

当某一个Fiber进入beginWork时,他最终的目的是为了创建当前Fiber节点的第一个子Fiber节点
首先,进入UpdateHostComponent,判断当前Fiber节点的类型,进入不同update的逻辑
在reconcileChildren逻辑中,判断workInProgressFiber是否存在对应的currentFiber来决定是否标记effectTag
reconcileChildFibers:根据child节点的类型执行创建操作
最终进入createFiber,创建一个Fiber树。
当进入reconcileChildrenArray,该函数创建当前Fiber节点的子节点

此时,returnFiber指的是header,即创建img节点,而不是img,p,a子Fiber节点

