React | 2020-07-05 21:58:14 616次 1次
上篇简述了它大概做的事情,本篇将继续进行展开来说明。
一、currentTime
const currentTime = requestCurrentTimeForUpdate(); export function requestCurrentTimeForUpdate() { // 不是render或者commit阶段,插个眼,暂时没明白! if ((executionContext & (RenderContext | CommitContext)) !== NoContext) { return msToExpirationTime(now()); } // 更新 if (currentEventTime !== NoWork) { // Use the same start time for all updates until we enter React again. return currentEventTime; } // 初次渲染 currentEventTime = msToExpirationTime(now()); return currentEventTime; }
这里用到了 msToExpirationTime 方法,传入一个当前时间,这个 now 方法先简要看下,其中调用的 Scheduler_now 方法,做了各个平台判断,这里我们只关心浏览器端,高版本浏览器中直接用的是 performance(从开始时间到调用它所经过的毫秒数),低版本中使用 Date.now 实现,目前这个 10000 的判断不确定作用,应该是和最大整数有关系,先可以将这个方法理解为 performance:
/** Scheduler_now * 低版本: * const initialTime = Date.now(); getCurrentTime = function() { return Date.now() - initialTime; }; 高版本: performance.now() */ let initialTimeMs: number = Scheduler_now(); export const now = initialTimeMs < 10000 ? Scheduler_now : () => Scheduler_now() - initialTimeMs;
export function msToExpirationTime(ms: number): ExpirationTime { return MAGIC_NUMBER_OFFSET - ((ms / UNIT_SIZE) | 0); }
MAGIC_NUMBER_OFFSET 就是计算机 1073741823 最大数减去了两次1得到,UNIT_SIZE 默认为 10,或零操作代表了只保留整数部分,比如 ms 的值从 20 - 29 区间,得到的 msToExpirationTime 结果都是一样的,十毫秒内的误差为零。
二、requestCurrentSuspenseConfig
目前值为 null
const suspenseConfig = requestCurrentSuspenseConfig();
三、computeExpirationForFiber
计算任务的过期时间,其中 current 就是 RootFiber。
const expirationTime = computeExpirationForFiber( currentTime, current, suspenseConfig, );
export function computeExpirationForFiber( currentTime: ExpirationTime, fiber: Fiber, suspenseConfig: null | SuspenseConfig, ): ExpirationTime { const mode = fiber.mode; //默认是任务不分片的 if ((mode & BlockingMode) === NoMode) { return Sync; } //任务优先级定义获取 const priorityLevel = getCurrentPriorityLevel(); ... let expirationTime; if (suspenseConfig !== null) { ... } else { // Compute an expiration time based on the Scheduler priority. switch (priorityLevel) { case ImmediatePriority: expirationTime = Sync; break; case UserBlockingPriority: // TODO: Rename this to computeUserBlockingExpiration expirationTime = computeInteractiveExpiration(currentTime); break; case NormalPriority: case LowPriority: // TODO: Handle LowPriority // TODO: Rename this to... something better. expirationTime = computeAsyncExpiration(currentTime); break; case IdlePriority: expirationTime = Idle; break; default: ... } } //渲染时禁止再触发更新 if (workInProgressRoot !== null && expirationTime === renderExpirationTime) { expirationTime -= 1; } return expirationTime; }
这个方法后续应该还会再分析,目前先简单了解下,根据不同的渲染模式和任务优先级,触发不同的逻辑。如下两个方法中调用的都是 computeExpirationBucket,参数的区别就是根据不同任务的优先级划分,这里说的优先级分为:
1. 立即执行优先级,立即过期
2. 用户阻塞型优先级,250毫秒后过期
3. 空闲优先级,永不过期,可以在任意空闲时间内执行
4. 普通优先级,5秒后过期
function computeInteractiveExpiration(currentTime: ExpirationTime) { return computeExpirationBucket( currentTime, HIGH_PRIORITY_EXPIRATION, // 150 HIGH_PRIORITY_BATCH_SIZE, // 100 ); } export function computeAsyncExpiration( currentTime: ExpirationTime, ): ExpirationTime { return computeExpirationBucket( currentTime, LOW_PRIORITY_EXPIRATION, // 5000 LOW_PRIORITY_BATCH_SIZE, // 250 ); }
function computeExpirationBucket( currentTime, expirationInMs, bucketSizeMs, ): ExpirationTime { return ( MAGIC_NUMBER_OFFSET - ceiling( MAGIC_NUMBER_OFFSET - currentTime + expirationInMs / UNIT_SIZE, //UNIT_SIZE = 10 bucketSizeMs / UNIT_SIZE, ) ); } //这个方法似曾相识,也是去除误差用的 function ceiling(num: number, precision: number): number { return (((num / precision) | 0) + 1) * precision; }
其中 ceiling 方法很简单,返回一个最小值为 precision,并按照这个值进行区间划分。难以理解的是上面这个函数的计算,按照低优先级模式先将公式套入简化:
1073741822 - ((((1073741822 - currentTime + 500)/ 25) | 0) + 1) * 25;
这样做为了合并更新,短时间内的多次更新不会立即触发,而是抹平误差后统一的时间点进行更新。ExpirationTime 值越大,优先级相对越高,对于立马过期的任务则需要立即执行(就算浏览器没有空闲时间,阻塞也要执行)。
四、createUpdate
更新对象,会被存储在上一篇介绍的 UpdateQueue 更新队列中。
export function createUpdate( expirationTime: ExpirationTime, suspenseConfig: null | SuspenseConfig, ): Update<*> { let update: Update<*> = { expirationTime, //deadline 时间,过期则更新 suspenseConfig, //suspense 配置 tag: UpdateState, // 更新类型,UpdateState、ReplaceState、ForceUpdate、CaptureUpdate payload: null, //状态变更函数或新状态本身 callback: null, //回调,作用于 fiber.effectTag,并将 callback 作为 side-effects 回调 next: (null: any), //指向下一个 Update }; update.next = update; ... return update; }
五、enqueueUpdate
使用 UpdateQueue 存储 Update 更新队列。更新队列有两条,baseQueue 执行中的更新队列,pendingQueue(即 shared.pending)待执行的更新队列。因为 Update 表现为环状单向链表,baseQueue、pendingQueue 均存储单向链表的尾节点。
React 是可中断的,先提前了解下,丢弃更新的实现在于将 pendingQueue 复制给 baseQueue, 丢弃之前的 baseQueue(current fiber 和 work-in-progress fiber 均会重置 baseQueue)。
export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) { // 这个 updateQueue 对象就是上一篇中创建的 const updateQueue = fiber.updateQueue; if (updateQueue === null) { // Only occurs if the fiber has been unmounted. return; } const sharedQueue = updateQueue.shared; const pending = sharedQueue.pending; if (pending === null) { // 初次渲染 这个 update在创建的时候就next指向自身了,可能是为了后续有更新 update.next = update; } else { //更新 插入一个更新节点改变next指向 update.next = pending.next; pending.next = update; } sharedQueue.pending = update; ... }
六、setState
在上面基本上完成了初始渲染的一些前期准备工作,接下来要做的就是进入 scheduleWork 进行调度,在进入调度分析之前先看下 react 中的更新操作,它和 updateContainer 中做的事情很类似,创建更新,存入队列,然后开始调度:
const classComponentUpdater = { isMounted, enqueueSetState(inst, payload, callback) { const fiber = getInstance(inst); const currentTime = requestCurrentTimeForUpdate(); const suspenseConfig = requestCurrentSuspenseConfig(); const expirationTime = computeExpirationForFiber( currentTime, fiber, suspenseConfig, ); const update = createUpdate(expirationTime, suspenseConfig); update.payload = payload; ... enqueueUpdate(fiber, update); scheduleWork(fiber, expirationTime); }, enqueueReplaceState(inst, payload, callback) { ... //标识 替换 update.tag = ReplaceState; ... }, enqueueForceUpdate(inst, callback) { ... ////标识 全量更新 update.tag = ForceUpdate; ... }, };
可见更新时做的事情都是一样的,通过不同的标识,下一篇将会进入调度阶段。
1人赞