在执行一些操作时,若需要遍历整棵dom树后才能更新,这就很低效了!若dom 树中有更新,React会给当前fiber添加effectTag,以标记变更。
effectTags
//packages\shared\ReactSideEffectTags.jsexport type SideEffectTag = number;// Don't change these two values. They're used by React Dev Tools.export const NoEffect = /* */ 0b000000000000;export const PerformedWork = /* */ 0b000000000001;// You can change the rest (and add more).export const Placement = /* */ 0b000000000010;export const Update = /* */ 0b000000000100;export const PlacementAndUpdate = /* */ 0b000000000110;export const Deletion = /* */ 0b000000001000;export const ContentReset = /* */ 0b000000010000;export const Callback = /* */ 0b000000100000;export const DidCapture = /* */ 0b000001000000;export const Ref = /* */ 0b000010000000;export const Snapshot = /* */ 0b000100000000;export const Passive = /* */ 0b001000000000;// Passive & Update & Callback & Ref & Snapshotexport const LifecycleEffectMask = /* */ 0b001110100100;// Union of all host effectsexport const HostEffectMask = /* */ 0b001111111111;export const Incomplete = /* */ 0b010000000000;export const ShouldCapture = /* */ 0b100000000000;
effectTag 的值是二进制,使用&、| 可以很方便计算出需要的操作,例如,Placement | Update = PlacementAndUpdate。
effect
在ReactFiber中对effect进行了注释:
// EffecteffectTag: SideEffectTag,// Singly linked list fast path to the next fiber with side-effects.nextEffect: Fiber | null,// The first and last fiber with side-effect within this subtree.// This allows us to reuse a slice of the linked list when we reuse the work// done within this fiber.firstEffect: Fiber | null,lastEffect: Fiber | null
effect添加
fiber是增量的。在workloop中会逐渐生成fiber,在workLoop中fiber是workInProgress。例如,在创建一个组件fiber,实例有componentDidUpdate时,则添加一个Update effect。
//ReactFiberClassComponent line:1057if (typeof instance.componentDidUpdate === 'function') {if (oldProps !== current.memoizedProps || oldState !== current.memoizedState ) {workInProgress.effectTag |= Update;}}if (typeof instance.getSnapshotBeforeUpdate === 'function') {if ( oldProps !== current.memoizedProps || oldState !== current.memoizedState) {workInProgress.effectTag |= Snapshot;}}
effect 链表
workloop的循环是不停的执行 performUnitOfWork。若当前的fiber没有子项时next=null则完成(complete)当前工作。
function performUnitOfWork(workInProgress){if (next === null) {// If this doesn't spawn new work, complete the current work.next = completeUnitOfWork(workInProgress);}}
在complete the current work时,会记一个 effect 的链表。
//在completeUnitOfWorkfunction completeUnitOfWork(workInProgress){//...省略代码if( workInProgress.effectTag & Incomplete) === NoEffect ){if (returnFiber !== null &&// 如果兄弟姐妹没有完成,不要给父母添加效果(returnFiber.effectTag & Incomplete) === NoEffect) {if (returnFiber.firstEffect === null) {//链表第一项一直透传returnFiber.firstEffect = workInProgress.firstEffect;}if (workInProgress.lastEffect !== null) {if (returnFiber.lastEffect !== null) {returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;}returnFiber.lastEffect = workInProgress.lastEffect;}const effectTag = workInProgress.effectTag;if (effectTag > PerformedWork) {if (returnFiber.lastEffect !== null) {returnFiber.lastEffect.nextEffect = workInProgress;} else {returnFiber.firstEffect = workInProgress;}returnFiber.lastEffect = workInProgress;}}}//...省略代码}
链表例子
例如,假如有3个effect,A、B、C有effect:
root.fiber
A
B C D
第一次B有effect时:
A.firstEffect = BworkInProgress;
A.lastEffect = BworkInProgress;
第二次C有effect时:
A.lastEffect.nextEffect = BworkInProgress.nextEffect = CworkInProgress;
A.lastEffect = CworkInProgress;
第三次D无effect时:
不操作
第四次A有effect时:
root.fiber.firstEffect = A.firstEffect = BworkInProgress;
root.fiber.lastEffect= CworkInProgress;
CworkInProgress.nextEffect = AworkInProgress;
root.fiber.lastEffect = AworkInProgress;
第五次由于returnFiber = null,所以不做处理。
此次的结果为:
| 层级 | firstEffect | lastEffect | nextEffect |
|---|---|---|---|
| root | BworkInProgress | AworkInProgress | null |
| A | BworkInProgress | CworkInProgress | |
| B | null | null | CworkInProgress |
| C | null | nul | AworkInProgress |
| D | null | null | nul |
commitRoot
workLoop中生成effect的链表,在commitRoot 时会用到链表做一些操作:
commitBeforeMutationLifecycles 会提交 hook 和 getSnapshotBeforeUpdate;
commitAllHostEffects 提交fibers到浏览器,执行完提交就可以在浏览器看到元素;
commitAllLifeCycles 提交组件的生命周期,componentDidMount 和 componentDidUpdate等在挂载后的effect;
function commitRoot(root,finishedWork){let firstEffect;//找到链表的第一项if (finishedWork.effectTag > PerformedWork) {if (finishedWork.lastEffect !== null) {finishedWork.lastEffect.nextEffect = finishedWork;firstEffect = finishedWork.firstEffect;} else {firstEffect = finishedWork;}} else {// There is no effect on the root.firstEffect = finishedWork.firstEffect;}//...省略代码nextEffect = firstEffect;startCommitSnapshotEffectsTimer();while (nextEffect !== null) {let didError = false;let error;try {commitBeforeMutationLifecycles();} catch (e) {didError = true;error = e;}}stopCommitSnapshotEffectsTimer();//之前的都是标记,此处做插入、删除、卸载nextEffect = firstEffect;startCommitHostEffectsTimer();while (nextEffect !== null) {let didError = false;let error;try {commitAllHostEffects();//此方法过后,可以看到dom元素} catch (e) {didError = true;error = e;}}stopCommitHostEffectsTimer();// placements, updates and deletions 在整个树中已经被调用nextEffect = firstEffect;startCommitLifeCyclesTimer();while (nextEffect !== null) {let didError = false;let error;try {commitAllLifeCycles(root, committedExpirationTime);//所有的声明周期} catch (e) {didError = true;error = e;}}isCommitting = false;isWorking = false;stopCommitLifeCyclesTimer();}
链表的应用
在commitAllHostEffects中是一个大的while循环,在循环中nextEffect。例如上面例子的执行的方式是:
nextEffect -> BworkInProgress,nextEffect = nextEffect.nextEffect;
nextEffect -> CworkInProgress,nextEffect = nextEffect.nextEffect;
nextEffect-> AworkInProgress,nextEffect = nextEffect.nextEffect;
nextEffect -> null,结束循环;
function commitAllHostEffects() {while (nextEffect !== null) {recordEffect();const effectTag = nextEffect.effectTag;if (effectTag & ContentReset) {commitResetTextContent(nextEffect);}if (effectTag & Ref) {const current = nextEffect.alternate;if (current !== null) {commitDetachRef(current);}}let primaryEffectTag = effectTag & (Placement | Update | Deletion);switch (primaryEffectTag) {case Placement: {commitPlacement(nextEffect);nextEffect.effectTag &= ~Placement;break;}case PlacementAndUpdate: {commitPlacement(nextEffect);nextEffect.effectTag &= ~Placement;const current = nextEffect.alternate;commitWork(current, nextEffect);break;}case Update: {const current = nextEffect.alternate;commitWork(current, nextEffect);break;}case Deletion: {commitDeletion(nextEffect);break;}}nextEffect = nextEffect.nextEffect;}}
