跳到主要内容

任务调度-草稿

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;