您现在的位置是:网站首页> 编程资料编程资料
React render核心阶段深入探究穿插scheduler与reconciler_React_
2023-12-09
278人已围观
简介 React render核心阶段深入探究穿插scheduler与reconciler_React_
本章将讲解 react 的核心阶段之一 —— render阶段,我们将探究以下部分内容的源码:
- 更新任务的触发
- 更新任务的创建
- reconciler 过程同步和异步遍历及执行任务
- scheduler 是如何实现帧空闲时间调度任务以及中断任务的
触发更新
触发更新的方式主要有以下几种:ReactDOM.render
、setState
、forUpdate
以及 hooks 中的 useState
等,关于 hooks 的我们后面再详细讲解,这里先关注前三种情况。
ReactDOM.render
ReactDOM.render
作为 react 应用程序的入口函数,在页面首次渲染时便会触发,页面 dom 的首次创建,也属于触发 react 更新的一种情况。
首先调用 legacyRenderSubtreeIntoContainer
函数,校验根节点 root 是否存在,若不存在,调用 legacyCreateRootFromDOMContainer
创建根节点 root、rootFiber 和 fiberRoot 并绑定它们之间的引用关系,然后调用 updateContainer
去非批量执行后面的更新流程;若存在,直接调用 updateContainer
去批量执行后面的更新流程:
// packages/react-dom/src/client/ReactDOMLegacy.js function legacyRenderSubtreeIntoContainer( parentComponent: ?React$Component, children: ReactNodeList, container: Container, forceHydrate: boolean, callback: ?Function, ) { // ... let root: RootType = (container._reactRootContainer: any); let fiberRoot; if (!root) { // 首次渲染时根节点不存在 // 通过 legacyCreateRootFromDOMContainer 创建根节点、fiberRoot 和 rootFiber root = container._reactRootContainer = legacyCreateRootFromDOMContainer( container, forceHydrate, ); fiberRoot = root._internalRoot; if (typeof callback === 'function') { const originalCallback = callback; callback = function() { const instance = getPublicRootInstance(fiberRoot); originalCallback.call(instance); }; } // 非批量执行更新流程 unbatchedUpdates(() => { updateContainer(children, fiberRoot, parentComponent, callback); }); } else { fiberRoot = root._internalRoot; if (typeof callback === 'function') { const originalCallback = callback; callback = function() { const instance = getPublicRootInstance(fiberRoot); originalCallback.call(instance); }; } // 批量执行更新流程 updateContainer(children, fiberRoot, parentComponent, callback); } return getPublicRootInstance(fiberRoot); }
updateContainer
函数中,主要做了以下几件事情:
- requestEventTime:获取更新触发的时间
- requestUpdateLane:获取当前任务优先级
- createUpdate:创建更新
- enqueueUpdate:将任务推进更新队列
- scheduleUpdateOnFiber:调度更新
关于这几个函数稍后会详细讲到
// packages/react-dom/src/client/ReactDOMLegacy.js export function updateContainer( element: ReactNodeList, container: OpaqueRoot, parentComponent: ?React$Component, callback: ?Function, ): Lane { // ... const current = container.current; const eventTime = requestEventTime(); // 获取更新触发的时间 // ... const lane = requestUpdateLane(current); // 获取任务优先级 if (enableSchedulingProfiler) { markRenderScheduled(lane); } const context = getContextForSubtree(parentComponent); if (container.context === null) { container.context = context; } else { container.pendingContext = context; } // ... const update = createUpdate(eventTime, lane); // 创建更新任务 update.payload = {element}; callback = callback === undefined ? null : callback; if (callback !== null) { // ... update.callback = callback; } enqueueUpdate(current, update); // 将任务推入更新队列 scheduleUpdateOnFiber(current, lane, eventTime); // schedule 进行调度 return lane; }
setState
setState 时类组件中我们最常用的修改状态的方法,状态修改会触发更新流程。
class 组件在原型链上定义了 setState
方法,其调用了触发器 updater
上的 enqueueSetState
方法:
// packages/react/src/ReactBaseClasses.js Component.prototype.setState = function(partialState, callback) { invariant( typeof partialState === 'object' || typeof partialState === 'function' || partialState == null, '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'); };
然后我们再来看以下 updater 上定义的 enqueueSetState
方法,一看到这我们就了然了,和 updateContainer
方法中做的事情几乎一模一样,都是触发后续的更新调度。
// packages/react-reconciler/src/ReactFiberClassComponent.old.js const classComponentUpdater = { isMounted, enqueueSetState(inst, payload, callback) { const fiber = getInstance(inst); const eventTime = requestEventTime(); // 获取更新触发的时间 const lane = requestUpdateLane(fiber); // 获取任务优先级 const update = createUpdate(eventTime, lane); // 创建更新任务 update.payload = payload; if (callback !== undefined && callback !== null) { if (__DEV__) { warnOnInvalidCallback(callback, 'setState'); } update.callback = callback; } enqueueUpdate(fiber, update); // 将任务推入更新队列 scheduleUpdateOnFiber(fiber, lane, eventTime); // schedule 进行调度 // ... if (enableSchedulingProfiler) { markStateUpdateScheduled(fiber, lane); } }, // ... };
forceUpdate
forceUpdate
的流程与 setState
几乎一模一样:
同样其调用了触发器 updater 上的 enqueueForceUpdate
方法,enqueueForceUpdate
方法也同样是触发了一系列的更新流程:相关参考视频讲解:传送门
reconciler/src/ReactFiberClassComponent.old.js const classComponentUpdater = { isMounted, // ... enqueueForceUpdate(inst, callback) { const fiber = getInstance(inst); const eventTime = requestEventTime(); // 获取更新触发的时间 const lane = requestUpdateLane(fiber); // 获取任务优先级 const update = createUpdate(eventTime, lane); // 创建更新 update.tag = ForceUpdate; if (callback !== undefined && callback !== null) { if (__DEV__) { warnOnInvalidCallback(callback, 'forceUpdate'); } update.callback = callback; } enqueueUpdate(fiber, update); // 将任务推进更新队列 scheduleUpdateOnFiber(fiber, lane, eventTime); // 触发更新调度 // ... if (enableSchedulingProfiler) { markForceUpdateScheduled(fiber, lane); } }, };
创建更新任务
可以发现,上述的三种触发更新的动作,最后殊途同归,都会走上述流程图中从 requestEventTime
到 scheduleUpdateOnFiber
这一流程,去创建更新任务,先我们详细看下更新任务是如何创建的。
获取更新触发时间
前面的文章中我们讲到过,react 执行更新过程中,会将更新任务拆解,每一帧优先执行高优先级的任务,从而保证用户体验的流畅。那么即使对于同样优先级的任务,在任务多的情况下该优先执行哪一些呢?
react 通过 requestEventTime
方法去创建一个 currentEventTime,用于标识更新任务触发的时间,对于相同时间的任务,会批量去执行。同样优先级的任务,currentEventTime 值越小,就会越早执行。
我们看一下 requestEventTime
方法的实现:
// packages/react-reconciler/src/ReactFiberWorkLoop.old.js export function requestEventTime() { if ((executionContext & (RenderContext | CommitContext)) !== NoContext) { // 在 react 执行过程中,直接返回当前时间 return now(); } // 如果不在 react 执行过程中 if (currentEventTime !== NoTimestamp) { // 正在执行浏览器事件,返回上次的 currentEventTime return currentEventTime; } // react 中断后首次更新,计算新的 currentEventTime currentEventTime = now(); return currentEventTime; }
在这个方法中,(executionContext & (RenderContext | CommitContext)
做了二进制运算,RenderContext
代表着 react 正在计算更新,CommitContext
代表着 react 正在提交更新。所以这句话是判断当前 react 是否处在计算或者提交更新的阶段,如果是则直接返回 now()
。
// packages/react-reconciler/src/ReactFiberWorkLoop.old.js export const NoContext = /* */ 0b0000000; const BatchedContext = /* */ 0b0000001; const EventContext = /* */ 0b0000010; const DiscreteEventContext = /* */ 0b0000100; const LegacyUnbatchedContext = /* */ 0b0001000; const RenderContext = /* */ 0b0010000; const CommitContext = /* */ 0b0100000; export const RetryAfterError = /* */ 0b1000000; let executionContext: ExecutionContext = NoContext;
再来看一下 now
的代码,这里的意思时,当前后的更新任务时间差小于 10ms 时,直接采用上次的 Scheduler_now
,这样可以抹平 10ms 内更新任务的时间差, 有利于批量更新:
// packages/react-reconciler/src/SchedulerWithReactIntegration.old.js export const now = initialTimeMs < 10000 ? Scheduler_now : () => Scheduler_now() - initialTimeMs;
综上所述,requestEvent
做的事情如下:
- 在 react 的 render 和 commit 阶段我们直接获取更新任务的触发时间,并抹平相差 10ms 以内的更新任务以便于批量执行。
- 当 currentEventTime 不等于 NoTimestamp 时,则判断其正在执行浏览器事件,react 想要同样优先级的更新任务保持相同的时间,所以直接返回上次的 currentEventTime
- 如果是 react 上次中断之后的首次更新,那么给 currentEvent
相关内容
- React运行机制超详细讲解_React_
- 使用JavaScript实现按钮的涟漪效果实例代码_javascript技巧_
- React深入分析useEffect源码_React_
- React.memo 和 useMemo 的使用问题小结_React_
- Vue+ ArcGIS JavaScript APi详解_vue.js_
- node+vue前后端分离实现登录时使用图片验证码功能_vue.js_
- React Fiber源码深入分析_React_
- vue3中使用Apache ECharts的详细方法_vue.js_
- React深入浅出分析Hooks源码_React_
- 天天酷跑糖白虎怎么抽奖_天天酷跑抽奖必中糖白虎小技巧分享_手机游戏_游戏攻略_