Hello, everyone. I am Karsong.

Act18 has entered the RC(Release candidate) stage and is only one step away from the official release.

There are a lot of new features in v18. Today, instead of talking about new features, we’ll talk about one detail that makes v18 better than its predecessor:

In V18, the component may render even less often

Welcome to join the Human high quality front-end framework research group, Band fly

Where the state comes from

In the following components:

function App() {
  const [num, update] = useState(0);
  / /... omit
}
Copy the code

The App component render executes useState, returning the latest value of num.

That is, the component must render to know the latest state. Why is that?

Consider the following code to trigger the update:

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

OnClick execution triggers an update that results in App component Render, which then useState execution.

Within useState, num is calculated as follows:

  1. Update (100) change num to 100

  2. Update (num => num + 1) Change num to 100 + 1 = 101

  3. Update (num => num * 3) change num to 101 * 3 = 303

Num is 303 when the App component is render.

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

For the above example, the updates are named u0 ~ U2 respectively, then the calculation formula of the status is:

baseState -> u0 -> u1 -> u2 = newState
Copy the code

Changes made by Concurrent

Concurrent introduces the concept of priority to React, which is reflected in state calculations, with updates having different priorities depending on the scenario in which they are triggered (for example, updates triggered in the onClick callback have higher priorities than updates triggered in the useEffect callback).

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

Assuming that U1 has a low priority in the above example, the formula for calculating num state when App component render is as follows:

// U1 is skipped because it has a low priority
baseState -> u0 -> u2 = newState
Copy the code

That is:

  1. Update (100) change num to 100

  2. Update (num => num * 3) change num to 100 * 3 = 300

Obviously this result is not correct.

As a result, React’s logic for calculating state in concurrent situations is more complex. Specifically, it may involve multiple rounds of calculation.

When calculating status, if an update is skipped, the next calculation continues from the skipped update.

For example, in the above example, u1 is skipped. Num is 100 when u1 is jumped. The state 100, u1 and all subsequent updates will be saved for the next calculation.

In this case, u1 and u2 are saved.

The next update will look like this:

  1. Update (num => num + 1) change num to 100 + 1 = 101

  2. Update (num => num * 3) change num to 101 * 3 = 303

As you can see, the final result 303 is the same as the synchronized React, but requires render twice.

React render once, resulting in 303.

React render the React render process twice, giving 300 (intermediate state) and 303 (final state).

The difference between old and new Concurrent

From the example above we see that the number of times a component renders depends on how many updates are skipped, and may actually render not twice, but multiple times.

In older versions of concurrent React, the priority was denoted by a timestamp called expirationTime. The algorithm for comparing whether updates should be skipped is as follows:

// Update priority is less than render priority
if (updateExpirationTime < renderExpirationTime) {
  / /... Is skipped
} else {
  / /... Don't skip
}
Copy the code

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

In the new concurrent React, priorities are stored in 31-bit binary numbers.

Here’s an example:

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

RenderLanes is the priority specified for this update.

The function of comparing priority is:

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

Among them:

// true
isSubsetOfLanes(renderLanes, u1.lane)

// false
isSubsetOfLanes(renderLanes, u2.lane)
Copy the code

U1. Lane is included in renderLanes, indicating that the update has sufficient priority.

U2. lane was not included in renderLanes, meaning the update did not have sufficient priority and was skipped.

But the skipped update (u2 in the example) will have its lane reset to 0, i.e. :

u2.lane = 0b0000;
Copy the code

Obviously any lanes contains 0:

// true
isSubsetOfLanes(renderLanes, 0)
Copy the code

So this update will definitely be handled next time. In other words, in the new concurrent React version, there will be no more than two repeat renders due to priority skipping.

conclusion

React has an advantage in the number of render times compared to the old concurrent React.

Reflected in the user’s senses, the user will see less of the incomplete intermediate state.