React 内核层
- 调度器
-
- Scheduler,核心指责,执行回调。
- 把 react-reconciler 提供的回调函数,包装到一个任务对象中。
- 内部维护一个任务队列,优先级高的排在最前面(最小堆)。
- 循环消费任务队列,直到队列清空。
- 构造器
-
- react-reconciler
- 接收react-dom和react发起的更新请求。
- 将 fiber 树的构造过程包装在一个回调函数中,并将此回调函数传入到 Scheduler 包等待调度。
- 渲染器
-
- react-dom 包,2 个核心指责
- 引导 react 应用的启动(通过ReactDOM.Render)
概览
Schedule负责调度 React 生成的任务,reconciler 负责生成 workInprogress 树并交给React-dom渲染到页面上。
常见问题
如果树构造到一半,没有时间片了怎么办。
首先构造到一半,没有时间片之后,是不会把一半的树渲染到页面上的。因为渲染的阶段是 commit,而在执行渲染的过程中会打上标记用来标识是否生成了整颗树。看下源码
var exitStatus = shouldTimeSlice
? renderRootConcurrent(root, lanes)
: renderRootSync(root, lanes);
/*KaSong*/ logHook("performConcurrentWorkOnRoot-exitStatus", exitStatus);
// 可以看到这里,如果exitStatus处于未完成的情况,启动了下一轮的调度,继续构建树。
// 所以不会出现构建一半的树出现在页面上的情况。因为没走到 commit 阶段
if (exitStatus !== RootIncomplete) {
if (exitStatus === RootErrored) {
}
if (exitStatus === RootFatalErrored) {
var fatalError = workInProgressRootFatalError;
prepareFreshStack(root, NoLanes);
markRootSuspended$1(root, lanes);
ensureRootIsScheduled(root, now());
throw fatalError;
} // Check if this render may have yielded to a concurrent event, and if so,
// confirm that any newly rendered stores are consistent.
// TODO: It's possible that even a concurrent render may never have yielded
// to the main thread, if it was fast enough, or if it expired. We could
// skip the consistency check in that case, too.
var renderWasConcurrent = !includesBlockingLane(root, lanes);
var finishedWork = root.current.alternate;
if (
renderWasConcurrent &&
!isRenderConsistentWithExternalStores(finishedWork)
) {
// A store was mutated in an interleaved event. Render again,
// synchronously, to block further mutations.
exitStatus = renderRootSync(root, lanes); // We need to check again if something threw
/*KaSong*/ logHook("performSyncWorkOnRoot-exitStatus", exitStatus);
if (exitStatus === RootErrored) {
var _errorRetryLanes = getLanesToRetrySynchronouslyOnError(root);
if (_errorRetryLanes !== NoLanes) {
lanes = _errorRetryLanes;
exitStatus = recoverFromConcurrentError(root, _errorRetryLanes); // We assume the tree is now consistent because we didn't yield to any
// concurrent events.
}
}
if (exitStatus === RootFatalErrored) {
var _fatalError = workInProgressRootFatalError;
prepareFreshStack(root, NoLanes);
markRootSuspended$1(root, lanes);
ensureRootIsScheduled(root, now());
throw _fatalError;
}
} // We now have a consistent tree. The next step is either to commit it,
// or, if something suspended, wait to commit it after a timeout.
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
finishConcurrentRender(root, exitStatus, lanes);
}
ensureRootIsScheduled(root, now());
可以看到用exitStatus进行了判断,满足要求才会进入到 if 语句里面,进而调用 commit,将树交给 ReactDom,最终渲染到页面上。如果是未完全构建的树,是不会进入 commit 阶段的。
触发更新的时机
拿 useState 举例,一个setState就是一个构造 Fiber 树的任务。在 setState内部启动了调度器用来调度任务。
function dispatchSetState(fiber, queue, action) {
// 开启调度。
var root = scheduleUpdateOnFiber(fiber, lane, eventTime);
}
开启了一次调度,然后schedule会根据此次任务的 lane 判断当前是同步更新还是并发更新
// 属于同步渲染,该模式下不可中断。
if (newCallbackPriority === SyncLane) {
scheduleMicrotask(flushSyncCallbacks);
}else{
// 并发更新
}
同步更新的话,是一个微任务,并且不可打断。并发更新的话,会根据优先级的不同,在宏任务中触发。并发更新的整个调度过程具有可恢复,可中断,可抢占的特性。