9
头图

Hello everyone, I'm Casson.

React18 has entered the RC (release candidate) stage, and is only one step away from the official version.

v18 added a lot of features. Today, we will not talk about new features, but will talk about a detail that is better than the old version of v18 :

In v18, the number of component renders may be less

Welcome to join human high-quality front-end framework group , with flying

where does the state come from

in the following components:

function App() {
  const [num, update] = useState(0);
  // ...省略
}

App component render will execute useState and return the latest value of num .

That is, the component must be render to know the latest state. Why is this so?

Consider the following code that triggers an update:

const [num, update] = useState(0);
const onClick = () => {
  update(100);
  update(num => num + 1);
  update(num => num * 3);
}

After the execution of onClick , the update is triggered, and the update causes the App component render , and then useState to execute.

Inside useState , num will be calculated according to the following process:

  1. update(100) turns num into 100
  2. update(num => num + 1) changes num to 100 + 1 = 101
  3. update(num => num * 3) changes num to 101 * 3 = 303

That is, when App component render , num is 303.

Therefore, the calculation of the state needs to collect the updates triggered by first, and then calculate them uniformly in useState .

For the above example, the updates are named u0~u2 respectively, and the calculation formula of the state is:

baseState -> u0 -> u1 -> u2 = newState

Changes brought by Concurrent

Concurrent (concurrency) brings the React to 06232ce710f25d, which is reflected in the state calculation . According to the scene that triggers the update, the update has different priorities (for example, the onClick callback useEffect triggered updates).

The difference that manifests in the computed state is that if an update has a low priority, it will be skipped.

Assuming that the priority of u1 in the above example is low, then when the App component is render , the formula for calculating the state of num is:

// 其中u1因为优先级低,被跳过
baseState -> u0 -> u2 = newState

which is:

  1. update(100) changes num to 100
  2. update(num => num * 3) changes num to 100 * 3 = 300

Obviously this result is wrong.

Therefore, the logic of React computing state under concurrent conditions will be more complicated. Specifically, multiple rounds of computation may be involved.

When calculating the state, if a update is skipped, the next calculation will continue from the skipped update.

For example, in the above example, u1 is skipped. num is 100 when u1 is skipped. The state 100 at this time, and all the updates after u1 and will be saved and participate in the next calculation.

In the example, u1 and u2 are saved.

The next update is as follows:

  1. The initial state is 100 , update(num => num + 1) changes num to 100 + 1 = 101
  2. update(num => num * 3) changes num to 101 * 3 = 303

It can be seen that the final result 303 is the same as the React synchronized with , but it only needs render twice.

synced React render once and the result is 303.

concurrent React render twice, the result is 300 (intermediate state), 303 (final state).

The difference between the old and the new Concurrent

From the above example, we found that the number of times of component render is affected by how many updates of are skipped by . Actually, it may not only be render twice, but many times.

In the old concurrent React , the priority is a timestamp called expirationTime . The algorithm to compare whether the update should be skipped is as follows:

// 更新优先级是否小于render的优先级
if (updateExpirationTime < renderExpirationTime) {
  // ...被跳过
} else {
  // ...不跳过
}

Under this logic, as long as the priority is low, it will be skipped, which means one more render .

In the new concurrent React , the priority is stored in the 31-bit binary .

for example:

const renderLanes = 0b0101;
u1.lane =           0b0001;
u2.lane =           0b0010;

Where renderLanes is the priority specified in this update.

The function to compare priority is:

function isSubsetOfLanes(set, subset) {
  return (set & subset) === subset;
}

in:

// true
isSubsetOfLanes(renderLanes, u1.lane)

// false
isSubsetOfLanes(renderLanes, u2.lane)

u1.lane included in renderLanes , which means this update has sufficient priority.

u2.lane is not included in renderLanes , which means that this update does not have enough priority and is skipped.

But the of the skipped update ( u2 in the example) by lane will be reset to 0, ie:

u2.lane = 0b0000;

Apparently any lanes contains 0:

// true
isSubsetOfLanes(renderLanes, 0)

So this update will definitely be processed next time. In other words, in the new version of concurrent React , is skipped due to the priority of , resulting in repeating render , at most 2 times.

Summarize

Compared with the old version of concurrent React , the new version of concurrent React will have more advantages in render times.

Reflected on the user's senses, the user will see less of the intermediate state is not fully .


卡颂
3.1k 声望16.7k 粉丝