跳到主要内容

生命周期和状态更新

生命周期调用顺序

各阶段生命周期执行情况

函数组件 hooks 的周期会在 hooks 章节讲解,这一章的使命周期主要针对类组件,各阶段生命周期执行情况看下图:

react源码11.1

  • render 阶段:
    1. mount 时:组件首先会经历 constructor、getDerivedStateFromProps、componnetWillMount、render
    2. update 时:组件首先会经历 componentWillReceiveProps、getDerivedStateFromProps、shouldComponentUpdate、render
    3. error 时:会调用 getDerivedStateFromError
  • commit 阶段
    1. mount 时:组件会经历 componnetDidMount
    2. update 时:组件会调用 getSnapshotBeforeUpdate、componnetDidUpdate
    3. unMount 时:调用 componnetWillUnmount
    4. error 时:调用 componnetDidCatch

其中红色的部分不建议使用,需要注意的是 commit 阶段生命周期在 mutation 各个子阶段的执行顺序,可以复习上一章

接下来根据一个例子来讲解在 mount 时和 update 时更新的具体顺序:

react源码11.2

react源码11.3

  • mount 时:首先会按照深度优先的方式,依次构建 wip Fiber 节点然后切换成 current Fiber,在 render 阶段会依次执行各个节点的 constructor、getDerivedStateFromProps/componnetWillMount、render,在 commit 阶段,也就是深度优先遍历向上冒泡的时候依次执行节点的 componnetDidMount
  • update 时:同样会深度优先构建 wip Fiber 树,在构建的过程中会 diff 子节点,在 render 阶段,如果返现有节点的变化,例如上图的 c2,那就标记这个节点 Update Flag,然后执行 getDerivedStateFromProps 和 render,在 commit 阶段会依次执行节点的 getSnapshotBeforeUpdate、componnetDidUpdate

状态更新流程

setState&forceUpdate

在 react 中触发状态更新的几种方式:

  • ReactDOM.render
  • this.setState
  • this.forceUpdate
  • useState
  • useReducer

我们重点看下重点看下 this.setState 和 this.forceUpdate,hook 在第 13 章讲

  1. this.setState 内调用 this.updater.enqueueSetState,主要是将 update 加入 updateQueue 中

    //ReactBaseClasses.js
    Component.prototype.setState = function (partialState, callback) {
    if (
    !(
    typeof partialState === "object" ||
    typeof partialState === "function" ||
    partialState == null
    )
    ) {
    {
    throw Error(
    "setState(...): takes an object of state variables to update or a function which returns an object of state variables."
    );
    }
    }
    this.updater.enqueueSetState(this, partialState, callback, "setState");
    };
    //ReactFiberClassComponent.old.js
    enqueueSetState(inst, payload, callback) {
    const fiber = getInstance(inst);//fiber实例

    const eventTime = requestEventTime();
    const suspenseConfig = requestCurrentSuspenseConfig();

    const lane = requestUpdateLane(fiber, suspenseConfig);//优先级

    const update = createUpdate(eventTime, lane, suspenseConfig);//创建update

    update.payload = payload;

    if (callback !== undefined && callback !== null) { //赋值回调
    update.callback = callback;
    }

    enqueueUpdate(fiber, update);//update加入updateQueue
    scheduleUpdateOnFiber(fiber, lane, eventTime);//调度update
    }

    enqueueUpdate 用来将 update 加入 updateQueue 队列

    //ReactUpdateQueue.old.js
    export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
    const updateQueue = fiber.updateQueue;
    if (updateQueue === null) {
    return;
    }

    const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;
    const pending = sharedQueue.pending;
    if (pending === null) {
    update.next = update; //与自己形成环状链表
    } else {
    update.next = pending.next; //加入链表的结尾
    pending.next = update;
    }
    sharedQueue.pending = update;
    }

    react源码12.6

  2. this.forceUpdate 和 this.setState 一样,只是会让 tag 赋值 ForceUpdate

    //ReactBaseClasses.js
    Component.prototype.forceUpdate = function (callback) {
    this.updater.enqueueForceUpdate(this, callback, "forceUpdate");
    };
    //ReactFiberClassComponent.old.js
    enqueueForceUpdate(inst, callback) {
    const fiber = getInstance(inst);
    const eventTime = requestEventTime();
    const suspenseConfig = requestCurrentSuspenseConfig();
    const lane = requestUpdateLane(fiber, suspenseConfig);

    const update = createUpdate(eventTime, lane, suspenseConfig);

    //tag赋值ForceUpdate
    update.tag = ForceUpdate;

    if (callback !== undefined && callback !== null) {
    update.callback = callback;
    }

    enqueueUpdate(fiber, update);
    scheduleUpdateOnFiber(fiber, lane, eventTime);

    },
    };

    如果标记 ForceUpdate,render 阶段组件更新会根据 checkHasForceUpdateAfterProcessing,和 checkShouldComponentUpdate 来判断,如果 Update 的 tag 是 ForceUpdate,则 checkHasForceUpdateAfterProcessing 为 true,当组件是 PureComponent 时,checkShouldComponentUpdate 会浅比较 state 和 props,所以当使用 this.forceUpdate 一定会更新

    //ReactFiberClassComponent.old.js
    const shouldUpdate =
    checkHasForceUpdateAfterProcessing() ||
    checkShouldComponentUpdate(
    workInProgress,
    ctor,
    oldProps,
    newProps,
    oldState,
    newState,
    nextContext
    );

    状态更新整体流程

    react源码12.1

Update&updateQueue

HostRoot 或者 ClassComponent 触发更新后,会在函数 createUpdate 中创建 update,并在后面的 render 阶段的 beginWork 中计算 Update。FunctionComponent 对应的 Update 在第 11 章讲,它和 HostRoot 或者 ClassComponent 的 Update 结构有些不一样

//ReactUpdateQueue.old.js
export function createUpdate(eventTime: number, lane: Lane): Update<*> {
//创建update
const update: Update<*> = {
eventTime,
lane,

tag: UpdateState,
payload: null,
callback: null,

next: null,
};
return update;
}

我们主要关注这些参数:

  • lane:优先级(第 12 章讲)
  • tag:更新的类型,例如 UpdateState、ReplaceState
  • payload:ClassComponent 的 payload 是 setState 第一个参数,HostRoot 的 payload 是 ReactDOM.render 的第一个参数
  • callback:setState 的第二个参数
  • next:连接下一个 Update 形成一个链表,例如同时触发多个 setState 时会形成多个 Update,然后用 next 连接

对于 HostRoot 或者 ClassComponent 会在 mount 的时候使用 initializeUpdateQueue 创建 updateQueue,然后将 updateQueue 挂载到 fiber 节点上

//ReactUpdateQueue.old.js
export function initializeUpdateQueue<State>(fiber: Fiber): void {
const queue: UpdateQueue<State> = {
baseState: fiber.memoizedState,
firstBaseUpdate: null,
lastBaseUpdate: null,
shared: {
pending: null,
},
effects: null,
};
fiber.updateQueue = queue;
}
  • baseState:初始 state,后面会基于这个 state,根据 Update 计算新的 state
  • firstBaseUpdate、lastBaseUpdate:Update 形成的链表的头和尾
  • shared.pending:新产生的 update 会以单向环状链表保存在 shared.pending 上,计算 state 的时候会剪开这个环状链表,并且链接在 lastBaseUpdate 后
  • effects:calback 不为 null 的 update

从触发更新的 fiber 节点向上遍历到 rootFiber

在 markUpdateLaneFromFiberToRoot 函数中会从触发更新的节点开始向上遍历到 rootFiber,遍历的过程会处理节点的优先级(第 15 章讲)

//ReactFiberWorkLoop.old.js
function markUpdateLaneFromFiberToRoot(
sourceFiber: Fiber,
lane: Lane
): FiberRoot | null {
sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);
let alternate = sourceFiber.alternate;
if (alternate !== null) {
alternate.lanes = mergeLanes(alternate.lanes, lane);
}
let node = sourceFiber;
let parent = sourceFiber.return;
while (parent !== null) {
//从触发更新的节点开始向上遍历到rootFiber
parent.childLanes = mergeLanes(parent.childLanes, lane); //合并childLanes优先级
alternate = parent.alternate;
if (alternate !== null) {
alternate.childLanes = mergeLanes(alternate.childLanes, lane);
} else {
}
node = parent;
parent = parent.return;
}
if (node.tag === HostRoot) {
const root: FiberRoot = node.stateNode;
return root;
} else {
return null;
}
}

例如 B 节点触发更新,B 节点被被标记为 normal 的 update,也就是图中的 u1,然后向上遍历到根节点,在根节点上打上一个 normal 的 update,如果此时 B 节点又触发了一个 userBlocking 的 Update,同样会向上遍历到根节点,在根节点上打上一个 userBlocking 的 update。

如果当前根节点更新的优先级是 normal,u1、u2 都参与状态的计算,如果当前根节点更新的优先级是 userBlocking,则只有 u2 参与计算

react源码12.5

调度

在 ensureRootIsScheduled 中,scheduleCallback 会以一个优先级调度 render 阶段的开始函数 performSyncWorkOnRoot 或者 performConcurrentWorkOnRoot

//ReactFiberWorkLoop.old.js
if (newCallbackPriority === SyncLanePriority) {
// 任务已经过期,需要同步执行render阶段
newCallbackNode = scheduleSyncCallback(
performSyncWorkOnRoot.bind(null, root)
);
} else {
// 根据任务优先级异步执行render阶段
var schedulerPriorityLevel =
lanePriorityToSchedulerPriority(newCallbackPriority);
newCallbackNode = scheduleCallback(
schedulerPriorityLevel,
performConcurrentWorkOnRoot.bind(null, root)
);
}

状态更新

classComponent 状态计算发生在 processUpdateQueue 函数中,涉及很多链表操作,看图更加直白

  • 初始时 fiber.updateQueue 单链表上有 firstBaseUpdate(update1)和 lastBaseUpdate(update2),以 next 连接

  • fiber.updateQueue.shared 环状链表上有 update3 和 update4,以 next 连接互相连接

  • 计算 state 时,先将 fiber.updateQueue.shared 环状链表‘剪开’,形成单链表,连接在 fiber.updateQueue 后面形成 baseUpdate

  • 然后遍历按这条链表,根据 baseState 计算出 memoizedState

    react源码12.2

带优先级的状态更新

类似 git 提交,这里的 c3 意味着高优先级的任务,比如用户出发的事件,数据请求,同步执行的代码等。

  • 通过 ReactDOM.render 创建的应用没有优先级的概念,类比 git 提交,相当于先 commit,然后提交 c3react源码12.3

  • 在 concurrent 模式下,类似 git rebase,先暂存之前的代码,在 master 上开发,然后 rebase 到之前的分支上

    优先级是由 Scheduler 来调度的,这里我们只关心状态计算时的优先级排序,也就是在函数 processUpdateQueue 中发生的计算,例如初始时有 c1-c4 四个 update,其中 c1 和 c3 为高优先级

    1. 在第一次 render 的时候,低优先级的 update 会跳过,所以只有 c1 和 c3 加入状态的计算
    2. 在第二次 render 的时候,会以第一次中跳过的 update(c2)之前的 update(c1)作为 baseState,跳过的 update 和之后的 update(c2,c3,c4)作为 baseUpdate 重新计算

    在在 concurrent 模式下,componentWillMount 可能会执行多次,变现和之前的版本不一致

    注意,fiber.updateQueue.shared 会同时存在于 workInprogress Fiber 和 current Fiber,目的是为了防止高优先级打断正在进行的计算而导致状态丢失,这段代码也是发生在 processUpdateQueue 中

react源码12.4

看 demo_8 的优先级

现在来看下计算状态的函数

//ReactUpdateQueue.old.js
export function processUpdateQueue<State>(
workInProgress: Fiber,
props: any,
instance: any,
renderLanes: Lanes
): void {
const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
hasForceUpdate = false;

let firstBaseUpdate = queue.firstBaseUpdate; //updateQueue的第一个Update
let lastBaseUpdate = queue.lastBaseUpdate; //updateQueue的最后一个Update
let pendingQueue = queue.shared.pending; //未计算的pendingQueue

if (pendingQueue !== null) {
queue.shared.pending = null;
const lastPendingUpdate = pendingQueue; //未计算的ppendingQueue的最后一个update
const firstPendingUpdate = lastPendingUpdate.next; //未计算的pendingQueue的第一个update
lastPendingUpdate.next = null; //剪开环状链表
if (lastBaseUpdate === null) {
//将pendingQueue加入到updateQueue
firstBaseUpdate = firstPendingUpdate;
} else {
lastBaseUpdate.next = firstPendingUpdate;
}
lastBaseUpdate = lastPendingUpdate;

const current = workInProgress.alternate; //current上做同样的操作
if (current !== null) {
const currentQueue: UpdateQueue<State> = (current.updateQueue: any);
const currentLastBaseUpdate = currentQueue.lastBaseUpdate;
if (currentLastBaseUpdate !== lastBaseUpdate) {
if (currentLastBaseUpdate === null) {
currentQueue.firstBaseUpdate = firstPendingUpdate;
} else {
currentLastBaseUpdate.next = firstPendingUpdate;
}
currentQueue.lastBaseUpdate = lastPendingUpdate;
}
}
}

if (firstBaseUpdate !== null) {
let newState = queue.baseState;

let newLanes = NoLanes;

let newBaseState = null;
let newFirstBaseUpdate = null;
let newLastBaseUpdate = null;

let update = firstBaseUpdate;
do {
const updateLane = update.lane;
const updateEventTime = update.eventTime;
if (!isSubsetOfLanes(renderLanes, updateLane)) {
//判断优先级是够足够
const clone: Update<State> = {
//优先级不够 跳过当前update
eventTime: updateEventTime,
lane: updateLane,

tag: update.tag,
payload: update.payload,
callback: update.callback,

next: null,
};
if (newLastBaseUpdate === null) {
//保存跳过的update
newFirstBaseUpdate = newLastBaseUpdate = clone;
newBaseState = newState;
} else {
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}
newLanes = mergeLanes(newLanes, updateLane);
} else {
//直到newLastBaseUpdate为null才不会计算,防止updateQueue没计算完
if (newLastBaseUpdate !== null) {
const clone: Update<State> = {
eventTime: updateEventTime,
lane: NoLane,

tag: update.tag,
payload: update.payload,
callback: update.callback,

next: null,
};
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}

newState = getStateFromUpdate(
//根据updateQueue计算state
workInProgress,
queue,
update,
newState,
props,
instance
);
const callback = update.callback;
if (callback !== null) {
workInProgress.flags |= Callback; //Callback flag
const effects = queue.effects;
if (effects === null) {
queue.effects = [update];
} else {
effects.push(update);
}
}
}
update = update.next; //下一个update
if (update === null) {
//重置updateQueue
pendingQueue = queue.shared.pending;
if (pendingQueue === null) {
break;
} else {
const lastPendingUpdate = pendingQueue;

const firstPendingUpdate =
((lastPendingUpdate.next: any): Update<State>);
lastPendingUpdate.next = null;
update = firstPendingUpdate;
queue.lastBaseUpdate = lastPendingUpdate;
queue.shared.pending = null;
}
}
} while (true);

if (newLastBaseUpdate === null) {
newBaseState = newState;
}

queue.baseState = ((newBaseState: any): State); //新的state
queue.firstBaseUpdate = newFirstBaseUpdate; //新的第一个update
queue.lastBaseUpdate = newLastBaseUpdate; //新的最后一个update

markSkippedUpdateLanes(newLanes);
workInProgress.lanes = newLanes;
workInProgress.memoizedState = newState;
}

//...
}