递阶段mount时流程
递阶段
首先从rootFiber
开始向下深度优先遍历。为遍历到的每个Fiber节点
调用beginWork
方法 接起来。
当遍历到叶子节点(即没有子组件的组件)时就会进入“归”阶段。
打断点
- 运行React项目
- 进入开发者模式,点击
Sources
,搜索beiginWork
和completeWork
并打上断点
Fiber节点类型
CacheComponent: 24, // Experimental
ClassComponent: 1,
ContextConsumer: 9,
ContextProvider: 10,
CoroutineComponent: -1, // Removed
CoroutineHandlerPhase: -1, // Removed
DehydratedSuspenseComponent: 18, // Behind a flag
ForwardRef: 11,
Fragment: 7,
FunctionComponent: 0,
HostComponent: 5,
HostPortal: 4,
HostRoot: 3, //
HostText: 6,
IncompleteClassComponent: 17,
IndeterminateComponent: 2,
LazyComponent: 16,
LegacyHiddenComponent: 23,
MemoComponent: 14,
Mode: 8,
OffscreenComponent: 22, // Experimental
Profiler: 12,
ScopeComponent: 21, // Experimental
SimpleMemoComponent: 15,
SuspenseComponent: 13,
SuspenseListComponent: 19, // Experimental
TracingMarkerComponent: 25, // Experimental - This is technically in 18 but we don't
// want to fork again so we're adding it here instead
YieldComponent: -1, // Removed
调试的代码
import logo from './logo.svg';
import './App.css';
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<App />
);
调试并观察
-
第一次执行
beginWork
函数参数:
-
current
我们可以根据
current.tag:3
判断Fiber的类型,HostRoot
当前应用的根节点
-
-
第二次执行
beginWork
函数
参数:-
current
此时我们发现current = null
,这是因为Fiber架构的双缓存机制,对于首次渲染,只有当前的根节点才有current
,而其他节点只存在workProgress
-
workInProgress
workInProgress.elementType:f App()
,证明当前节点为App
函数式组件workInProgress.pedndingProps
,存储标签的属性(src,className,alt等)和子节点
-
-
第三次执行
beginWorl
函数参数:
-
workInProgress
workInProgress.elementType:'div'
,证明当前节点为div
-
-
…
-
当
workInProgress.elementType:'img'
执行完毕后,因为<img/>
标签下没有子节点,所以<img>
节点会执行completeWork
-
执行
p
节点的beiginWork
-
…
-
and save to reload.
文本节点执行完beginWork
后,本身没有子节点且没有下一个兄弟节点,所以会执行其父节点的completeWork
-
…
-
执行
App的completeWork
-
执行
HostRoot的completeWork
总结:
从根节点开始,执行
beginWork
函数,父节点执行完后,子节点执行当节点无子节点,执行
completeWork
函数,该节点的下一个兄弟节点执行beginWork
函数当节点无子节点且无下一个兄弟节点,会执行其父节点的
completeWork
注意:
<code>src/App.js</code>
当<code>
节点执行完beginWork
函数后,不会进入src/App.js
该文本节点的beginWork
,而是进入co mpleteWork
,workInProgress.elementType:'code'
解释:
react对于只有唯一个文本子节点的节点做出了优化,在这种情况下,这种文本节点不会生成自己的Fiber节点
beginWork
执行过程(以<div>
节点为例子):
-
根据当前
workInProgress.tag
进入不同的caseswitch (workInProgress.tag) { case IndeterminateComponent: { return mountIndeterminateComponent(current, workInProgress, workInProgress.type, renderLanes); } case LazyComponent: { var elementType = workInProgress.elementType; return mountLazyComponent(current, workInProgress, elementType, renderLanes); } case FunctionComponent: { var Component = workInProgress.type; var unresolvedProps = workInProgress.pendingProps; var resolvedProps = workInProgress.elementType === Component ? unresolvedProps : resolveDefaultProps(Component, unresolvedProps); return updateFunctionComponent(current, workInProgress, Component, resolvedProps, renderLanes); } case ClassComponent: { var _Component = workInProgress.type; var _unresolvedProps = workInProgress.pendingProps; var _resolvedProps = workInProgress.elementType === _Component ? _unresolvedProps : resolveDefaultProps(_Component, _unresolvedProps); return updateClassComponent(current, workInProgress, _Component, _resolvedProps, renderLanes); } case HostRoot: return updateHostRoot(current, workInProgress, renderLanes); case HostComponent: return updateHostComponent(current, workInProgress, renderLanes); .....
-
div
的tag:5
,根据Fiber类型映射可得,div
为HostComponent
类型,执行updateHostComponent
函数 -
updateHostComponent
函数内部(部分)// 检测当前的fiber节点是否只有唯一的文本子节点,如 <code>src/App.js</code> var isDirectTextChild = shouldSetTextContent(type, nextProps);
reconcileChildren(current, workInProgress, nextChildren, renderLanes); return workInProgress.child;
在未执行
reconcileChildren
之前,workInPorgress.child = null
-
进入
reconcileChildren
函数function reconcileChildren(current, workInProgress, nextChildren, renderLanes) { if (current === null) { // If this is a fresh new component that hasn't been rendered yet, we // won't update its child set by applying minimal side-effects. Instead, // we will add them all to the child before it gets rendered. That means // we can optimize this reconciliation pass by not tracking side-effects. workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes); } 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, renderLanes); } }
我们查看源码,发现
mountChildFibers
与reconcileChildFibers
调用的同一个函数,只是参数不同export const reconcileChildFibers: ChildReconciler = createChildReconciler(true); export const mountChildFibers: ChildReconciler = createChildReconciler(false);
-
进入
createChildReconciler
函数function createChildReconciler(shouldTrackSideEffects){ .... }
shouldTrackSideEffects
意为 是否追踪副作用react中通过flags记录每一个节点diff后需要变更的状态,例如:dom的增添、替换、删除等等
// packages/react-reconciler/src/ReactFiberFlags.js // 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 ChildDeletion = /* */ 0b00000000000000000000001000; export const ContentReset = /* */ 0b00000000000000000000010000; export const Callback = /* */ 0b00000000000000000000100000; export const DidCapture = /* */ 0b00000000000000000001000000; export const ForceClientRender = /* */ 0b00000000000000000010000000; export const Ref = /* */ 0b00000000000000000100000000; export const Snapshot = /* */ 0b00000000000000001000000000; export const Passive = /* */ 0b00000000000000010000000000; export const Hydrating = /* */ 0b00000000000000100000000000; export const Visibility = /* */ 0b00000000000001000000000000; export const StoreConsistency = /* */ 0b00000000000010000000000000; 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 = /* */ 0b00000000000011111111111111; // These are not really side effects, but we still reuse this field. export const Incomplete = /* */ 0b00000000000100000000000000; export const ShouldCapture = /* */ 0b00000000001000000000000000; export const ForceUpdateForLegacySuspense = /* */ 0b00000000010000000000000000; export const DidPropagateContext = /* */ 0b00000000100000000000000000; export const NeedsPropagation = /* */ 0b00000001000000000000000000; export const Forked = /* */ 0b00000010000000000000000000; // 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 = /* */ 0b00000100000000000000000000; export const LayoutStatic = /* */ 0b00001000000000000000000000; export const PassiveStatic = /* */ 0b00010000000000000000000000; // Flag used to identify newly inserted fibers. It isn't reset after commit unlike `Placement`. export const PlacementDEV = /* */ 0b00100000000000000000000000; export const MountLayoutDev = /* */ 0b01000000000000000000000000; export const MountPassiveDev = /* */ 0b10000000000000000000000000;
在
render
阶段不会执行dom操作,具体的dom操作是在commit
阶段执行的,render
阶段需要做的是为需要执行dom操作的fiber节点打上标记,比如:这个fiber节点对应的dom节点需要插入在页面中,它会为fiber节点打上Placement
标记为什么用二进制的形式来表示EffectTag?
解释:
const NoFlags = /* */ 0b00000000000000000000000000; const Placement = /* */ 0b00000000000000000000000010; const Update = /* */ 0b00000000000000000000000100; // 开始是没有副作用的 let effectTag = NoFlags // 打上标识 effect |= Placement effect |= Update
-
进入
mountChildFibers
函数-
判断
newChild
的类型,根据不同的类型,执行不同的代码-
为对象类型且不为null
-
为文本或数字
-
为数组
-
-
因为
div
节点的newChild.$$typeof = REACT_ELEMENT_TYPE
所以执行,reconcileSingleElement(returnFiber, currentFirstChild, newChild, lanes)
function reconcileSingleElement(returnFiber, currentFirstChild, element, lanes)
-
判断
currentFirstChild
是否存在var child = currentFirstChild;
-
判断该节点的子节点的类型并进入
createFiberFromElement
函数if (element.type === REACT_FRAGMENT_TYPE) { var created = createFiberFromFragment(element.props.children, returnFiber.mode, lanes, element.key); created.return = returnFiber; return created; } else { var _created4 = createFiberFromElement(element, returnFiber.mode, lanes); _created4.ref = coerceRef(returnFiber, currentFirstChild, element); _created4.return = returnFiber; return _created4; }
-
通过
createFiberFromElement
创建fiber节点export function createFiberFromElement( element: ReactElement, mode: TypeOfMode, lanes: Lanes, ): 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, lanes, ); if (__DEV__) { fiber._debugSource = element._source; fiber._debugOwner = element._owner; } return fiber; }
-
进入
createFiberFromTypeAndProps
函数,此时传入的信息全是header
节点的信息createFiberFromTypeAndProps
函数中,type
为header
,所以fiberTag = HostComponent
-
进入
createFiber
函数/** * tag=5 * pendingProps={ * className:'App-header', * children:[{...},{...},{...}] * } */ var createFiber = function (tag, pendingProps, key, mode) { return new FiberNode(tag, pendingProps, key, mode); };
-
进入
FiberNode
,创建Fiber
-
源代码
function reconcileChildFibers( returnFiber: Fiber, currentFirstChild: Fiber | null, newChild: any, lanes: Lanes, ): Fiber | null { // This function is not recursive. // If the top level item is an array, we treat it as a set of children, // not as a fragment. Nested arrays on the other hand will be treated as // fragment nodes. Recursion happens at the normal flow. // Handle top level unkeyed fragments as if they were arrays. // This leads to an ambiguity between <>{[...]}</> and <>...</>. // We treat the ambiguous cases above the same. const isUnkeyedTopLevelFragment = typeof newChild === 'object' && newChild !== null && newChild.type === REACT_FRAGMENT_TYPE && newChild.key === null; if (isUnkeyedTopLevelFragment) { newChild = newChild.props.children; } // Handle object types if (typeof newChild === 'object' && newChild !== null) { switch (newChild.$$typeof) { case REACT_ELEMENT_TYPE: return placeSingleChild( reconcileSingleElement( returnFiber, currentFirstChild, newChild, lanes, ), ); case REACT_PORTAL_TYPE: return placeSingleChild( reconcileSinglePortal( returnFiber, currentFirstChild, newChild, lanes, ), ); case REACT_LAZY_TYPE: const payload = newChild._payload; const init = newChild._init; // TODO: This function is supposed to be non-recursive. return reconcileChildFibers( returnFiber, currentFirstChild, init(payload), lanes, ); } if (isArray(newChild)) { return reconcileChildrenArray( returnFiber, currentFirstChild, newChild, lanes, ); } if (getIteratorFn(newChild)) { return reconcileChildrenIterator( returnFiber, currentFirstChild, newChild, lanes, ); } throwOnInvalidObjectType(returnFiber, newChild); } if ( (typeof newChild === 'string' && newChild !== '') || typeof newChild === 'number' ) { return placeSingleChild( reconcileSingleTextNode( returnFiber, currentFirstChild, '' + newChild, lanes, ), ); } if (__DEV__) { if (typeof newChild === 'function') { warnOnFunctionType(returnFiber); } } // Remaining cases are all treated as empty. return deleteRemainingChildren(returnFiber, currentFirstChild); }
-
-