在执行一些操作时,若需要遍历整棵dom树后才能更新,这就很低效了!若dom 树中有更新,React会给当前fiber添加effectTag,以标记变更。
effectTags
//packages\shared\ReactSideEffectTags.js
export 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 & Snapshot
export const LifecycleEffectMask = /* */ 0b001110100100;
// Union of all host effects
export const HostEffectMask = /* */ 0b001111111111;
export const Incomplete = /* */ 0b010000000000;
export const ShouldCapture = /* */ 0b100000000000;
effectTag 的值是二进制,使用&、| 可以很方便计算出需要的操作,例如,Placement | Update = PlacementAndUpdate。
effect
在ReactFiber中对effect进行了注释:
// Effect
effectTag: 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:1057
if (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 的链表。
//在completeUnitOfWork
function 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;
}
}