跳到主要内容

任务调度-草稿

React16源码解析3-Scheduler任务调度器​

react会根据生成elements的类型(就是下面调用instance.render()返回的,render()获得新的elements子节点tree,并为子节点创建fiber(创建过程会尽量复用现有fiber,elements的类型没有变就可以进行拷贝复制,也可以说创建的过程就是通过一边对比element tree与ord fiber一边打effects 来收集副作用用于更新到真实dom,子节点增删修改就是收集到这个effects里))。

https://zhuanlan.zhihu.com/p/110789300​

workLoop 循环方式​

workLoop​

循环单元更新,对整颗 fiberTree 都遍历一遍。

还记得之前传入进来的isYieldy的么,如果为false,不可中断,不断的更新下一个节点任务(performUnitOfWork(nextUnitOfWork)),知道整棵树更新完毕。如果可以中断,通过shouldYield()判断当前帧是否还有时间更新,有时间就更新,没有时间了就不更了。

function workLoop(isYieldy) {
// 对 nextUnitOfWork 循环进行判断,直到没有 nextUnitOfWork
if (!isYieldy) {
// 不可中断
// Flush work without yielding
while (nextUnitOfWork !== null) {
// 一开始进来 nextUnitOfWork 是 root,每次执行 performUnitOfWork 后
// 都会生成下一个工作单元
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
} else {
// 可中断
// Flush asynchronous work until the deadline runs out of time.
while (nextUnitOfWork !== null && !shouldYield()) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
}
}

performUnitOfWork​

1、调用beginWork()更新当前任务节点 2、如果当前fiber树已经更新到叶子节点了,则调用completeUnitOfWork更新。

// 开始组件更新
function performUnitOfWork(workInProgress: Fiber): Fiber | null {
// 获得 fiber 的替身,调和这一阶段都是在替身上完成的
// 然后直接看 beginWork
const current = workInProgress.alternate;

// ......
let next;

// .....
// 开始工作
next = beginWork(current, workInProgress, nextRenderExpirationTime);
workInProgress.memoizedProps = workInProgress.pendingProps;

// ......

// 当前fiber树已经更新到叶子节点了
if (next === null) {
// If this doesn't spawn new work, complete the current work.
next = completeUnitOfWork(workInProgress);
}

ReactCurrentOwner.current = null;

return next;
}

beginWork​

// 判断fiber有无更新,有更新则进行相应的组件更新,无更新则复制节点 next = beginWork(current, unitOfWork, renderExpirationTime);

只要 beginWork 有返回值,那么下次循环执行的就还是 beginWork,只有当 beginWork 全部执行完毕后,才会开始执行 completeUnitOfWork。

beginWork,从root 向下遍历,进行组件(节点)更新。

beginWork 方法虽然长,但是逻辑比较简单,它首先判断这个 Fiber 对象的优先级,不够高的会调用 bailoutOnAlreadyFinishedWork 方法跳过该节点及所有子节点的更新,不再往下执行组件的更新,否则根据 Fiber 对象的 tag 分类更新。

beginWork 的执行是从父节点到子节点,注意,这里的子节点只是值第一个子节点,我们可以发现,beginWork 在执行过程中,并没有从第一个子节点通过 sibling 跳转到它的兄弟节点。而是先一直往下遍历。

completeUnitOfWork​

completeUnitOfWork 是在 beginWork 返回 null 之后执行的,也就是 Fiber 树已经遍历到了叶子节点,或者是叶子节点不需要更新。

// 当从上到下遍历完成后,completeUnitOfWork 会从下到上根据effact tag进行一些处理 next = completeUnitOfWork(unitOfWork);

bailoutOnAlreadyFinishedWork​

有更新,但是优先级不高,在本次渲染过程中不需要执行 cloneChildFibers 直接把原来的节点拷贝即可

beginWork 进入 updateClassComponent 更新前可以判断fiber是否需要更新​

 // 判断 props 和 context 是否改变
// 判断当前 fiber 的优先级是否小于本次渲染的优先级,小于的话可以跳过
if (
oldProps === newProps &&
!hasLegacyContextChanged() &&
(updateExpirationTime === NoWork ||
updateExpirationTime > renderExpirationTime)
) {
// ......
}
// bailoutOnAlreadyFinishedWork 会判断这个 fiber 的子树是否需要更新,如果有需要更新会 clone 一份到 workInProgress.child 返回到 workLoop 的 nextUnitOfWork, 否则为 null
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}

beginWork对ClassComponent更新。​

这个函数的作用是:

  • 对未初始化的类组件进行初始化,对已经初始化的组件更新重用。
  • 完成组件实例的 state、props 的更新; 执行 componentWillUpdate、shouldComponentUpdate等生命周期函数; 完成组件实例的渲染;
  • 返回下一个待处理的任务单元;

若在render之前多次调用了setState,则会产生多个update对象。这些update对象会以链表的形式存在queue中。而在ClassComponent内部最后会有函数处理批量更新:

let updateQueue = workInProgress.updateQueue 取出更新队列进行批量更新。

processUpdateQueue内部会有一个while循环对这个更新队列进行依次遍历,并计算出最终要更新的状态state。

updateClassComponent​

updateClassComponent 属于 workloop 中的 beginWork 阶段,在这个阶段:

  • ClassComponent 执行了新建(更新)实例,绑定 update,
  • 执行componentWillMount,getDerivedStateFromProps,componentWillReceiveProps,shouldComponentUpdate 生命周期方法
  • 执行了 updateQueue 中的每个 update。由于在生命周期方法中可能修改 state,在生命周期结束后,会再次调用 updateQueue,完成 state 的更新。
  • 期间,还更新了 workInProgress 的 effectTag,比如,如果有 componentDidMount 或者 componentDidUpdate 方法,workInProgress.effectTag |= Update;如果 setState 有回调,会更新 workInProgress.effectTag |= Callback。
  • 最后调用 reconcileChildren 进行子节点的调和。
 /**
* 1. 完成组件实例的state、props的更新;
* 2. componentWillUpdate、shouldComponentUpdate生命周期函数执行完毕;
* 3. 获取是否要进行更新的标识shouldUpdate;
*/
shouldUpdate = updateClassInstance(
current,
workInProgress,
Component,
nextProps,
renderExpirationTime,
);

/**
* 1. 如果shouldUpdate值为false,则退出渲染;
* 2. 执行render函数
*/
const nextUnitOfWork = finishClassComponent(
current,
workInProgress,
Component,
shouldUpdate,
hasContext,
renderExpirationTime,
);

// 返回下一个任务单元
return nextUnitOfWork;

finishClassComponent​

调用instance的render产生ELement 的 DOM Tree​

调用render()获得新的子节点,并为子节点创建fiber(创建过程会尽量复用现有fiber,子节点增删也发生在这里)

let nextChildren;
if (
didCaptureError &&
typeof Component.getDerivedStateFromError !== 'function'
) {
nextChildren = null;
if (enableProfilerTimer) {
stopProfilerTimerIfRunning(workInProgress);
}
} else {
if (__DEV__) {
setCurrentPhase('render');
nextChildren = instance.render();
if (
debugRenderPhaseSideEffects ||
(debugRenderPhaseSideEffectsForStrictMode &&
workInProgress.mode & StrictMode)
) {
instance.render();
}
setCurrentPhase(null);
} else {
nextChildren = instance.render();
}
}

这个方法的最后会调用reconcileChildren进入diff算法,这里会进入孩子节点兄弟间的遍历比对​

 workInProgress.effectTag |= PerformedWork;
// 开始 diff 算法,生成新的 children
if (current !== null && didCaptureError) {
forceUnmountCurrentAndReconcile(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
} else {
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
}

保存state​

  workInProgress.memoizedState = instance.state;

// The context might have changed so we need to recalculate it.
if (hasContext) {
invalidateContextProvider(workInProgress, Component, true);
}

### 最后往下遍历
// 最后把新的第一个 child 返回出去作为下一个工作节点
return workInProgress.child;