You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
functioncommitMutationEffects(root,renderPriorityLevel){while(nextEffect!==null){varprimaryFlags=flags&(Placement|Update|Deletion|Hydrating);switch(primaryFlags){casePlacementAndUpdate: {// PlacementcommitPlacement(nextEffect);// Clear the "placement" from effect tag so that we know that this is// inserted, before any life-cycles like componentDidMount gets called.nextEffect.flags&=~Placement;// Updatevar_current=nextEffect.alternate;commitWork(_current,nextEffect);break;}caseHydratingAndUpdate: {nextEffect.flags&=~Hydrating;// Updatevar_current2=nextEffect.alternate;commitWork(_current2,nextEffect);break;}caseUpdate: {var_current3=nextEffect.alternate;commitWork(_current3,nextEffect);break;}}nextEffect=nextEffect.nextEffect;}}functioncommitWork(current,finishedWork){switch(finishedWork.tag){caseFunctionComponent: {// 调用函数组件 useLayoutEffect 的清除函数commitHookEffectListUnmount(Layout|HasEffect,finishedWork);return;}caseHostComponent: {if(instance!=null){varupdatePayload=finishedWork.updateQueue;if(updatePayload!==null){// 更新真实的DOM节点的属性commitUpdate(instance,updatePayload,type,oldProps,newProps);}}return;}caseHostText: {varoldText=current!==null ? current.memoizedProps : newText;commitTextUpdate(textInstance,oldText,newText);// 更新 textInstance.nodeValue = newTextreturn;}}}
位操作
在开始介绍 fiber flags 前,先来看下位操作
按位非(~)
按位非运算符(~),反转操作数的位。
按位非运算时,任何数字 x 的运算结果都是 -(x + 1)。例如,〜-5 运算结果为 4。
按位与(&)
按位与运算符 (&) 在两个操作数对应的二进位都为 1 时,该位的结果值才为 1,否则为 0。
按位或(|)
按位或运算符 (|) 在两个操作数对应的二进位只要有一个为 1 时,该位的结果值为 1,否则为 0。
按位异或(^)
有且仅有一个为 1 时,结果才为 1,否则为 0:
React 为什么采用二进制表示副作用
原因可以归类为以下两点:
我们先来看下使用其他方式表示副作用会有什么问题。假设我们使用 2 表示插入,在 render 阶段,如果这个 fiber 节点是新的,我们就给这个 fiber 节点添加一个副作用:
fiber.flags = 2
。然后在 commit 阶段使用fiber.flags === 2
判断节点是否需要插入。这会带来一个问题,React 中一个 fiber 节点会有多个副作用,比如,既可以是插入,又可以是更新(类组件实现了 componentDidMount 方法,就是更新的副作用),如果使用十进制,我们可以很容易想到这样实现:
在 commit 阶段就可以这样判断:
这样做理论上是可以的,但是数组操作比较麻烦,还会冗余,比如,如果多次
fiber.flags.push(2)
就会有多个重复的 2。同时如果需要先删除插入的副作用,并添加一个更新的副作用,操作起来较繁琐因此 React 采用了二进制标记这些副作用。不仅占用内存小,运算迅速,同时还能表示多个副作用
如果一个 fiber 节点,既要插入又要更新,可以这样标记:
如果需要删除一个插入的副作用,并且添加一个更新的副作用,那么可以这样标记:
可以说是相当的方便了
Fiber flags
PerformedWork
是专门提供给 React Dev Tools 读取的。fiber 节点的副作用从 2 开始。0 表示没有副作用。对于原生的 HTML 标签,如果需要修改属性,文本等,就视为有副作用。对于类组件,如果类实例实现了
componentDidMount
、componentDidUpdate
等生命周期方法,则视为有副作用。对于函数组件,如果实现了useEffect
、useLayoutEffect
等 hook,则视为有副作用。以上这些都是副作用的例子。React 在 render 阶段给有副作用的节点添加标志,并在 commit 阶段根据 fiber flags 执行对应的副作用操作,比如调用生命周期方法,或者操作真实的 DOM 节点。
React 支持的所有 flags
flags 位操作
这里简单列举一下 fiber flags 中一些位操作的含义。
Placement
render 阶段
reconcile children
过程中,如果节点需要移动,插入,则在placeChild
以及placeSingleChild
方法中将 fiber 标记为Placement
:本质上,创建新的 fiber 节点,也是一种 Placement 的副作用,即在 commit 阶段需要插入。因此,在类组件的
updateClassComponent
方法中判断 fiber 节点如果是新创建的,则标记为Placement
在懒加载的
mountLazyComponent
方法中,以及在函数组件第一次执行的mountIndeterminateComponent
方法中,判断 fiber 节点如果是新创建的,则标记为Placement
commit 阶段
commit 阶段执行 Placement 副作用操作。Placement 对应的副作用操作是插入新的 DOM 节点。插入节点的逻辑都在
commitMutationEffects
方法以及commitPlacement
方法中Update
render 阶段
mountClassInstance
方法中判断类组件如果实现了 componentDidMount 方法updateClassInstance
方法中判断如果类组件实现了componentDidUpdate
方法updateHostComponent
方法中调用prepareUpdate
方法判断 HostComponent 的属性如果发生了变更updateHostText
方法中判断如果新旧文本不同completeWork
方法中,判断如果 HostComponent 需要聚焦useEffect
、useLayoutEffect
这两个 hookcommit 阶段
Update 副作用执行的逻辑在
commitMutationEffects
以及commitLayoutEffects
两个方法中:commitMutationEffects 方法,用于执行 commitWork:
Deletion
render 阶段
reconcile children
过程中,deleteChild
判断节点如果需要被删除commit 阶段
ContentReset
render 阶段
commit 阶段
Callback
render 阶段
processUpdateQueue
判断如果 update.callback 不为 nullcommit 阶段
Snapshot
render 阶段
commit 阶段
Passive
render 阶段
useEffect
(注意,useLayoutEffect 并不属于 Passive 的副作用)commit 阶段
The text was updated successfully, but these errors were encountered: