Real expiration time

Posted by amir on Tue, 11 Jan 2022 10:18:08 +0100

Expiration Time concept

First, what is Expiration Time? According to the English direct translation, the Expiration Time or Expiration Time. How to understand the concept of Expiration Time in React? We might as well start with its role to understand what it is.

Expiration Time action

In real, the source code location is to call computeExpirationForFiber at the updateContainer location in the preparation phase to calculate the time. Here, the update object of real is created in the preparation phase to prepare for the subsequent real scheduling. It represents the point in time at which the task should be executed in the future, otherwise it will expire. See reactfiberexpirationtime in the React reconciler package for details JS specific code content

To sum up: in the process of creating updates, react reasonably arranges the update sequence for subsequent update scheduling. React will set an Expiration Time. When the Expiration Time arrives, it will force the update.

Specific source code content

Because the source code versions are different, there will be little difference. No specific analysis will be made here

export function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): ExpirationTime {

  // Gets the currently updated Fiber node
  const current = container.current;
  // Get current time
  const currentTime = requestCurrentTime();
  // Calculate ExpirationTime
  const expirationTime = computeExpirationForFiber(currentTime, current);
  return updateContainerAtExpirationTime(
    element,
    container,
    parentComponent,
    expirationTime,
    callback,
  );
}
Copy code

How to calculate Expiration Time

First, let's look at the Expiration Time code, which only involves the calculation method

/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt';

export type ExpirationTime = number;

export const NoWork = 0;
export const Sync = 1;
export const Never = MAX_SIGNED_31_BIT_INT;

const UNIT_SIZE = 10;
const MAGIC_NUMBER_OFFSET = 2;

// 1 unit of expiration time represents 10ms.
export function msToExpirationTime(ms: number): ExpirationTime {
  // Always add an offset so that we don't clash with the magic number for NoWork.
  return ((ms / UNIT_SIZE) | 0) + MAGIC_NUMBER_OFFSET;
}

export function expirationTimeToMs(expirationTime: ExpirationTime): number {
  return (expirationTime - MAGIC_NUMBER_OFFSET) * UNIT_SIZE;
}

function ceiling(num: number, precision: number): number {
  return (((num / precision) | 0) + 1) * precision;
}

// Core content
function computeExpirationBucket(
  currentTime,
  expirationInMs,
  bucketSizeMs,
): ExpirationTime {
  // currentTime is the current timestamp
  return (
    MAGIC_NUMBER_OFFSET +
    ceiling(
      currentTime - MAGIC_NUMBER_OFFSET + expirationInMs / UNIT_SIZE,
      bucketSizeMs / UNIT_SIZE,
    )
  );
}


export const LOW_PRIORITY_EXPIRATION = 5000;
export const LOW_PRIORITY_BATCH_SIZE = 250;

// Common asynchronous type
export function computeAsyncExpiration(
  currentTime: ExpirationTime,
): ExpirationTime {
  return computeExpirationBucket(
    currentTime,
    LOW_PRIORITY_EXPIRATION,
    LOW_PRIORITY_BATCH_SIZE,
  );
}

export const HIGH_PRIORITY_EXPIRATION = __DEV__ ? 500 : 150;
export const HIGH_PRIORITY_BATCH_SIZE = 100;

// Interactive type
export function computeInteractiveExpiration(currentTime: ExpirationTime) {
  return computeExpirationBucket(
    currentTime,
    HIGH_PRIORITY_EXPIRATION,
    HIGH_PRIORITY_BATCH_SIZE,
  );
}
Copy code

When you see the code, you can see two types of Expiration Time. One is ordinary asynchronous and the other is Interactive. For example, if Interactive is triggered by events, its response priority will be higher because it involves interaction.

Ex amp les & core content

Let's take a random type as an example. The currentTime 5000 250 is passed into the computeExpirationBucket. This involves a method. ceiling can be understood as a rounding method. Finally ((((currentTime - 2 + 5000 / 10) / 25) | 0) + 1) * 25 can be obtained, where 25 is 250 / 10 and| 0 is the function of rounding

What is the meaning of the formula? The previous currentTime - 2 + 5000 / 10 is relatively fixed, which is equal to the current time + 498

then ➗ 25 rounded and then ➕ 1 again × five

Finally (current time + 498) ➗ 25 rounded and then ➕ 1 again × five

Add 498 to the current time, then round it by 25, add 1, and multiply it by 5. It should be noted that the currentTime here is processed by msToExpirationTime, that is ((now / 10) | 0) + 2, so the subtraction of 2 here can be ignored, and the rounding by 10 should smooth out the error of 10 milliseconds, Of course, when it is finally used to calculate the time difference, it will call expirationTimeToMs to recover, but the 10 millisecond error removed by rounding must not be recovered

Simply put, here, the final result increases in units of 25. For example, if we input between 10002 and 10026, the final result is 10525, but the result of 10027 is 10550, which is the effect of dividing by 25.

Another thing to mention is the msToExpirationTime and expirationTimeToMs methods. They want to change the transformation relationship. One important thing to note here is that the currentTime used to calculate the expirationTime is obtained through msToExpirationTime(now), that is, it has been processed in advance. First, 10 and then 2 are added. Here 2 is magicNumberOffset, so it can be understood that the later calculation of expirationTime needs to subtract 2

Unit concept

Code first

export const HIGH_PRIORITY_EXPIRATION = __DEV__ ? 500 : 150;
export const HIGH_PRIORITY_BATCH_SIZE = 100;

export const LOW_PRIORITY_EXPIRATION = 5000;
export const LOW_PRIORITY_BATCH_SIZE = 250;
Copy code

The 25 mentioned above is a time unit. The expiration time calculated in this time unit is the same. React is to use the same expiration time for the updated contents in the same time unit. In this way, the updates will be merged (there will be opportunities to share later). It is assumed that if there is no unit concept, each call will create an update, There is no priority order, which will waste performance and affect efficiency. In this way, expiration time has priority to facilitate subsequent scheduling updates.

Summary

The design of React is equivalent to smoothing the error of calculating the expiration time within 25ms. The purpose of this is to obtain the same expiration time for two very detailed updates, and then complete it in one update, which is equivalent to an automatic batch update

The above is the calculation method of expirationTime. The following two will share the introduction of each expiration time in the source code

Additional content

In React, we calculate expirationTime based on the current clock time. Generally speaking, we only need to obtain date Now or performance Now yes, but each time you get it, it consumes performance. Therefore, React sets currentRendererTime to record this value for some scenes that do not need to be recalculated.

However, in ReactFiberScheduler, the currentSchedulerTime variable is also provided to record this value. Let's take a look at the implementation of the requestCurrentTime method. You can see why by looking at the comments here. You can directly return the latest time

if (isRendering) {
  // We're already rendering. Return the most recently read time.
  return currentSchedulerTime;
}
Copy code

This isRendering is set to true only when performWorkOnRoot is executed. It is a synchronous method. It does not exist. It jumps out when isRendering is not set to false halfway through execution. Under what circumstances will a new requestCurrentTime appear here?

  • The setState method is invoked in the lifecycle method.
  • When a task needs to be suspended
if (
  nextFlushedExpirationTime === NoWork ||
  nextFlushedExpirationTime === Never
) {
  // If there's no pending work, or if the pending work is offscreen, we can
  // read the current time without risk of tearing.
  recomputeCurrentRendererTime();
  currentSchedulerTime = currentRendererTime;
  return currentSchedulerTime;
}
Copy code

In other words, in a batch update, the time will be recalculated only when the update is created for the first time, and all subsequent updates will reuse the time when the update is created for the first time. This is also to ensure that the same type of updates generated in a batch update will only have the same expiration time

last

If you think this article is a little helpful to you, give it a compliment. Or you can join my development exchange group: 1025263163 learn from each other, and we will have professional technical Q & A to solve doubts

If you think this article is useful to you, please click star: http://github.crmeb.net/u/defu Thank you very much!

PHP learning manual: https://doc.crmeb.com
Technical exchange forum: https://q.crmeb.com

Topics: PHP github React .NET crmeb