This article is published under a SIGNATURE 4.0 International (CC BY 4.0) license.

Author: Baiying front-end team @0O O0 first published at juejin.cn/post/702299…

preface

Currently, the latest version of Act18 is available at 18.0.0-alpha-F6ABF4B40-20211020, and it will not be long before act18 is officially available. The biggest change from version 17 to version 18 is that the previously experimental Concurrent feature is now officially available. Therefore, we will use this article to talk about Concurrent mode, understand the usage and internal principles of Concurrent mode, and familiarize ourselves with Concurrent mode in advance so that we can better prepare for the coming of React18 in the future.

The directory structure of the whole article is as follows:

  • A preliminary Concurrent Mode
    • Concurrent & Legacy
    • Meaning of Concurrent Mode
  • How is fiber Tree coordination scheduled in Concurrent mode
    • The browser eventLoop
    • React task scheduling – workLoop
    • Coordinated scheduling under Legacy and Concurrent
  • Processing policies for different priorities of updates in Concurrent mode
    • Update mechanism for the React component state
    • lane
    • Lane in Legacy mode
    • Lane in Concurrent mode
  • Side effects caused by Concurrent mode
    • Side effects of repeated component coordination
    • Update side effects of repeated processing
  • Write in the last
  • portal
  • Reference documentation

Experience Concurrnt Mode for the first time

The core process of react update is fiber Tree coordination. Through coordination, we can find the changed Fiber nodes in the Fiber Tree and adjust the dom tree structure of the page to a minimum extent.

How to coordinate a Fiber Tree with a Binary tree react: How to coordinate a fiber Tree with a binary tree React: How to react with a binary tree

For coordination, React provides two modes: Legacy mode – synchronous blocking mode and Concurrent mode – parallel mode.

The difference between the two modes is whether the coordination process of fiber Tree can be interrupted. Legacy mode, the coordination process cannot be interrupted; Concurrent mode, the coordination process can be interrupted.

Concurrent & Legacy

There are two different modes, and we’ll try them out with two simple demos.

Legency mode: Legency – Demo

Concurrent mode: concurrent-demo

In this example, react and react-dom use the latest version 18.0.0-alpha-f6ABf4b40-20211020.

The above example is only used to illustrate how to enable the two modes, but does not visually show the difference between the two modes. Here, we need to run both modes in combination with the performance capabilities provided by the browser. Using the Performance analyzer, we can easily see the difference between the two modes.

  • Legacy mode

    The performance analysis chart of Legence-Demo is as follows:

    In Legacy mode, a complete React update is performed as follows:

    During the whole process, if the structure of the Fiber Tree is very complex, coordinating the Fiber Tree may take a lot of time, resulting in the main thread will always be occupied by the JS engine, rendering engine cannot complete the work within the specified time (browser refresh rate -16.7ms). The page appears to stall (drop frames), affecting the user experience.

  • Concurrent mode

    The performance analysis diagram of concurrent-Demo is as follows:

    In Concurrent mode, a complete React update is performed as follows:

    In contrast to Legacy mode and Concurrent mode, the JS engine does not hold the main thread all the time and automatically cedes the main thread when the specified time is reached.

    Specifically, the whole coordination process is carried out in segments, each time period is 5ms. If the coordination process does not end within the specified 5ms, the JS engine automatically cedes the main thread. React asks the browser to allocate a 5ms time slice to continue the coordination process since the coordination is not finished. The whole process is repeated until the coordination is complete.

    In Concurrent mode, the main thread is periodically released by the JS engine, which improves user experience to some extent compared with legacy mode.

In addition, careful students may notice that in legence-Demo and concurrent-demo above, both Legacy mode and Concurrent mode, we use the createRoot API to start the React application. We don’t use the render method that we’re so familiar with.

The only difference between the two demos is that the Legency – Demo calls setState directly in the click event callback method, while concurrent-Demo calls setState via useTransition. React uses Legacy mode to coordinate the Fiber Tree. UseTransition to call setState, react uses Concurrent mode to coordinate fiber tree.

The reason for this is that the upcoming Act18 will no longer support Render (not recommended) and we’ll need to launch a React app via createRoot. After the app starts, React determines which mode to use to coordinate the Fiber Tree based on the context in which updates are triggered. React uses Legacy mode if updates are triggered in callback events, setTimeout, or Network Request, while updates related to Suspense, useTransition, or OffScreen React will adopt Concurrent mode.

Meaning of Concurrent mode

The greatest significance of Concurrent mode is to improve the user experience of applications through interruptible coordination. Using Concurrent mode, our React application can do the following:

  • Coordination does not block browser rendering for long periods of time;

  • High-priority updates can interrupt low-priority updates and give priority to rendering;

  • Better interactive experience with new apis like Suspense, SuspenseList, useTransition, useDeferredValue (this will be covered in a future article);

Example: Coordination does not block browser rendering

blocking

non-blocking

Example: High-priority update interrupts low-priority update

interrupt

How is fiber Tree coordination scheduled in Concurrent mode

In the first experience of Concurrent Mode above, we learned that the coordination process of fiber Tree in Concurrent Mode is interruptible and that high-priority updates interrupt low-priority updates.

  • How is the coordination process interrupted?
  • How is the interrupted coordination process restored?
  • How is the time slice needed for coordination determined?
  • How do I request a new slice when the slice is used up?
  • How to determine the priority of updates?
  • How do high-priority updates interrupt low-priority updates?
  • What about interrupted low-priority updates?

With these questions in mind, let’s begin this chapter. I believe that after reading this chapter, you will basically have the answers to these questions mentioned above.

The browser eventLoop

Before we start this section, we need to talk about eventLoop for the browser.

There is already so much information on eventLoop that I won’t cover it in detail in this article. Here, post a link that I think is better: eventLoop. If you are interested, you can learn by yourself.

In this video, there is the most crucial legend:

This is the most iconic model of eventLoop. In this model, the middle part is eventLoop, the left part uses a JS engine to process tasks in the message queue, and the right part is a rendering engine to draw pages. The JS engine and rendering engine work mutually exclusive, and only one engine can occupy the main thread at a time.

This model is also needed in the later react scheduling, so we redraw it based on this model as follows:

We know that with setTimeout, you can add a macro task to the message queue. When the macro task is processed, the corresponding callback is processed by the JS engine.

Based on this, we can implement time fragmentation and re-request time slices via eventLoop.

If a JS program is not finished within the specified time, we can take the initiative to end it, and then request a new time slice through setTimeout to continue processing the unfinished task in the next time slice.

The pseudo-code for the whole process is as follows:

let taskQueue = [...] ; // let shouldTimeEnd = 5ms; Function processTaskQueue() {let beginTime = performance.now(); While (true) {let currentTime = performance.now(); If (currentTime - beginTime >= shouldTimeEnd) break; ProcessTask (); } if (taskQueue.length) {setTimeout(processTaskQueue, 0); }} processTaskQueue();Copy the code

This is a very simple scheduling model of time slice and re-request time slice.

In the above model, we use setTimemout. One problem with setTimemout is that although we set the delay time to 0ms, there will still be a delay of about 4ms, so the next time slice cannot be obtained as soon as possible. Based on this, we can use MessageChannel instead of setTimeout. Using MessageChannel, there is only about a 1ms delay, which helps us get to the next slice as quickly as possible.

The pseudocode using MessageChannel is as follows:

let taskQueue = [...] ; // let shouldTimeEnd = 5ms; // a time slice is defined as 5ms let channel = new MessageChannel(); Function processTaskQueue() {let beginTime = performance.now(); While (true) {let currentTime = performance.now(); If (currentTime - beginTime >= shouldTimeEnd) break; // The time slice has expired. ProcessTask (); / / time not mature, continue processing task} the if (taskQueue. Length) {/ / time expires, by calling the postMessage, request a time slice channel. The port2. PostMessage (null); } } channel.port1.onmessage = processTaskQueue; ProcessTaskQueue ();Copy the code

React time fragmentation and re-request time slice are implemented based on MessageChannel. The basic process is the same as the above pseudo code. If you are interested in the source code, you can check it out.

(✌🏻, time sharding and rerequest time slices resolved!)

The browser’s own task scheduling has no concept of priority and follows a FIFO policy, that is, the tasks in the message queue are processed first. In reality, users’ interactive operations are prioritized. For example, once clicked, users will expect to see the results quickly and have a higher priority. Switch pages, can be delayed a little, priority is not high. In order to schedule tasks based on priorities, React implements a prioritized workLoop based on eventLoop.

React Task scheduling – wookLoop

The react wookLoop is a simplified version of the pseudo-code we used to talk about time slicing and rerequesting time slicing.

let taskQueue = []; // let shouldTimeEnd = 5ms; // a time slice is defined as 5ms let channel = new MessageChannel(); Function wookLoop() {let beginTime = performance.now(); While (true) {let currentTime = performance.now(); If (currentTime - beginTime >= shouldTimeEnd) break; // The time slice has expired. ProcessTask (); / / time not mature, continue processing task} the if (taskQueue. Length) {/ / time expires, by calling the postMessage, request a time slice channel. The port2. PostMessage (null); } } channel.port1.onmessage = wookLoop; WorkLoop ();Copy the code

React maintains a taskQueue, just like eventLoop in the browser, and then iterates through the taskQueue using workLoop to process tasks in the taskQueue.

Tasks in a taskQueue are processed in sequence. Each time workLoop processes a task in the taskQueue, it selects the task with the highest priority for processing.

The entire process, similar to eventLoop, is illustrated as follows:

So how does this whole process work?

To answer this question, we need to be clear about three things:

  • How is the React task created?
  • How is the react task priority determined?
  • How do I get the highest priority task in the taskQueue?

Create a React task

When we click in the browser, the corresponding event handler does not fire immediately. The browser generates a scheduling task for the click event and adds it to the message queue, whose callback is the handler for the click event. The handler that clicked on the event is triggered when the eventLoop starts processing the dispatch task.

Similarly, the React update operation is the same.

Each react update triggers a fiber Tree coordination, but coordination does not sync immediately when the update triggers. Instead, react generates a task for this update and adds it to the taskQueue, with the Fiber Tree coordination method acting as the callback for the new task. When the wookLoop starts processing the task, the task callback is triggered and fiber Tree coordination begins.

React task priority

React updates trigger in different contexts.

We might use setState to trigger updates in handlers for click, Input, mousemove, etc., or in callback for network requests, It is also possible to trigger updates when using useTransition, even when Suspense blocks recovery, and so on.

Different update contexts represent different priorities and determine the priorities of corresponding tasks.

React internally defines five types of priorities:

  • ImmediatePriority (ImmediatePriority), corresponding to user operations such as click, input, and focus;
  • UserBlockingPriority, UserBlockingPriority, corresponding to user mousemove, scroll, etc.
  • NormalPriority: indicates the NormalPriority of network requests and useTransition operations.
  • LowPriority, LowPriority (application scenario not found);
  • IdlePriority, IdlePriority, such as OffScreen;

The sequence of the five priorities is as follows: ImmediatePriority > UserBlockingPriority > NormalPriority > LowPriority > IdlePriority.

Context Context Context context context Context Context Context Context Context Context Context Context Context Context Context Context Context Context Context Context Context Context Context Context Context Then determine the order of task processing according to the expirationTime time.

Timeouts are different with different priorities:

  • ImmediatePriority, timeout was -1, indicating that the task should be processed as soon as possible.
  • UserBlockingPriority, timeout 250 ms;
  • NormalPriority, timeout is 5000 ms.
  • LowPriority, timeout is 10000 ms.
  • IdlePriority, timeout is 1073741823 ms.

One question is, since tasks are already prioritized, why not use priority to determine the order in which tasks are processed? Tasks of higher priority are processed first, and tasks of lower priority are processed later. Tasks of the same priority are processed in the sequence in which they are created.

There is a problem with using only priority to determine the order in which tasks are processed. If workLoop processes tasks in a taskQueue, the main thread needs to be cleared and the task in the taskQueue needs to be processed in the next slice. However, the allocation of time slices is determined by the browser eventLoop, which is not controlled by the developer. Therefore, the allocation of the next time slice may be delayed for a long time, which may affect the user experience. With expirationTime, wookLoop will also determine if the next task to be processed is expired when the expirationTime expires. If the task has expired, the main thread cannot be relinquished and needs immediate processing.

Every task has a expirationTime, so it is logical that you can use expirationTime to determine the order in which tasks are processed (currently only understood this layer, 😂).

(✌🏻, if deciding update priorities problem solved!)

Gets the task that is processed first

React uses a minimum heap to store tasks, i.e. taskQueue is a minimum heap.

The minimum heap, as the name implies, is the smallest element at the top of the heap, which is reflected in the task scheduling, that is, the tasks placed on the top of the heap need to be processed first.

With minimal heap, there are three operations: push, POP, and PEEK.

Push, which adds tasks to the taskQueue. When a newly created task is added, the task is added to the bottom of the minimum heap, and then the minimum heap is adjusted from the bottom up. Context When you adjust, you compare the expirationTime of a heap node (Task) and adjust the task with a smaller expirationTime upwards.

Peek, get the heaptop element, that is, get the task that needs to be processed first, execute the callback of the task, and start fiber Tree coordination.

Pop, the top element of the heap out of the heap, that is, the task is finished and removed from the taskQueue. Once the top element is removed, the bottom element is placed at the top of the heap and the minimum heap is adjusted from the top down. When you adjust, you also compare the expirationTime of a heap node (Task) and adjust a task with a larger expirationTime downward.

The React minimum heap is an array based implementation. The react minimum heap is an array based implementation. The react minimum heap is an array based implementation.

Finally, we combine workLoop and eventLoop to help you understand react task scheduling:

Coordinated scheduling under Legacy and Concurrent

When the workLoop starts processing tasks in the taskQueue, the task callback is triggered to initiate fiber Tree coordination.

Like eventLoop and React task scheduling workLoop, the coordination process of Fiber Tree is also a Loop.

workLoopSync & workLoopConcurrent

The Loop works differently depending on the mode of coordination. In Legacy mode, workLoopSync is enabled. In Concurrent mode, it is workLoopConcurrent.

The entire working process of workLoopSync/workLoopConcurrent can be illustrated by the following legend:

In the coordination process, we need to do depth-first traversal of the Fiber Tree, so we can use a stack to simulate the processing of the Fiber node (the source does not use stack). In this article, we use the middle order traversal of the binary tree to sort out the coordination process of the Fiber tree. We use the middle order traversal of the binary tree to sort out the coordination process of the Fiber tree.

WorkLoopSync corresponds to Legacy mode. If the update is triggered in the Event, setTimeout, or Network Request callback, then workLoopSync will be started during coordination. After workLoopSync starts working, the work is not complete until all fiber nodes collected in the stack are processed, that is, the coordination process of the Fiber Tree cannot be interrupted.

WorkLoopConcurrent Corresponds to the Concurrent mode. If updates are related to Suspense, useTransition, OffScreen, then workLoopConcurrent will be started when coordinated. After workLoopConcurrent starts working, each time it coordinates fiber nodes, it determines whether the current time slice expires. If the time slice expires, the current workLoopConcurrent and workLoop are stopped, the main thread is released, and the next time slice is requested to continue coordination.

Combined with eventLoop and workLoop above, the whole process is as follows:

React interruptible rendering – Concurrent, 😄 by combining eventLoop, workLoop, workLoopSync/workLoopConcurrent.

(✌🏻, coordination how to interrupt problems solved!)

Resume interrupted coordination

In Concurrent mode, when the coordination process is interrupted, the global Pointers workInProgressRoot and workInProgress are used to record the root node and fiber node of the fiber tree being processed.

After the next time slice arrives, continue processing the interrupted task. According to workInProgressRoot and workInProgress, find the position where the last coordination ended and continue the unfinished coordination.

(✌🏻, how to restore interrupted coordination problem solved!)

High-priority updates interrupt low-priority updates

In Concurrent mode, if a high-priority update is entered during the coordination of low-priority updates, the high-priority update interrupts the coordination of low-priority updates.

The whole process is as follows:

Each time a new slice is received, workLoopConcurrent determines whether the priority of the coordination corresponding to this slice is the same as that of the coordination interrupted by the expiration of the last slice. If the same, it indicates that no update of higher priority is generated and the last unfinished coordination can be continued. If not, it indicates that a higher priority update is coming in. In this case, you need to clear the coordination process that has been started before and re-coordinate from the root node. Once the high-priority update processing is complete, low-priority updates are processed again from the root node.

(✌🏻, high priority updates how to break low priority updates, blocked low priority updates how to handle problem solved!)

The above paragraph is probably very difficult to understand, because we have an important concept – Lane has not been explained. Lane will be explained in more detail in the next section and will be easier to understand when you come back to it.

That’s the end of this section. With the collaboration of eventLoop, workLoop, workLoopSync/workLoopConcurrent, react updates in Concurrent mode can be implemented in a timely manner.

Next, let’s look at the handling strategy for different priority updates in Concurrent mode.

Processing policies for different priorities of updates in Concurrent mode

Before we start this section, let’s take a look at a demo, as follows:

In the example, we call setState twice in the handler for the input change event, once directly to modify the value of the short list; Change the value of the long list using useTransition once. It is obvious that the rendering of the long list will stall when the value of the input output box changes.

The React input box changes when the value of the react input box changes.

The reason why the rendering of the long list in the example above is stuck is that the long list has 10,000 nodes to render, which causes the layout process to take too long.

In the panel, we can clearly see that two setStates, two React updates, and two Fiber Tree coordination are triggered. Value1 is updated on the first coordination and value2 is updated on the second coordination.

Before the first coordination, we had committed two setstates in different contexts. How did React select the correct state for each coordination?

With this question in mind, let’s begin this section. I believe that after reading this section, you will find the answer.

Update mechanism for the React component state

First, let’s look at the update mechanism for component state.

The React component is the smallest unit of work for a React application.

Typically, when we define a non-presentation component, whether a class or a function component, we define one or more states for the component, then change the state by calling setState, triggering the React update, and then rerendering the page.

Calling setState does not immediately update component state. The update of state actually occurs in the coordination process of Fiber Tree, which is as follows:

In the example demo, two setState objects, update1 and update2, are generated. Update1 corresponds to the update of short list Value1 and update2 corresponds to the update of long list Value2. The two Update objects are added to update Ue in the order in which they were created.

The update object is processed at different times due to different contexts in which the update object is created. For the first time, process update1 and update value1; On the second coordination, update2 is processed and value2 is updated.

This is because different contexts bind different lanes to the UPDATE object. Lane determines when the Update object should be processed.

lane

Lane.

We can think of an update as a car, and a lane is the lane assigned to the update car. Different lanes are defined for update in different contexts, modeled as follows:

The priority of a lane varies with context. React internally defines 31 lanes, in descending order of priority:

React uses a 31-bit binary number to represent lane, as follows:

const SyncLane = 1; // 0000 0000 0000 0000 0000 0000 0000 0001 const DefaultLane = 16; // 0000 0000 0000 0000 0000 0000 0000 0001 0000Copy the code

The smaller the number of bits corresponding to lane, the higher the priority. For example, if SyncLane is 1, it has the highest priority. OffscreenLane is 31, with the lowest priority.

In addition to the update lane, there are two other lane concepts to understand: Lanes for fiber nodes and pendingLanes for the root node of the Fiber Tree.

Each update generates update objects and allocates lanes. The generated Update objects collect lanes in the updateQueue, and the allocated lanes collect lanes in the component fiber node. Collection methods are as follows:

fiberNode.lanes = 0; fiberNode.lanes = fiberNode.lanes | SyncLane; / / lanes for 1 fiberNode. Lanes. = fiberNode lanes | DefaultLane; / / lanes of 17Copy the code

Using lanes on a Fiber node, you can see whether the fiber node has updates that need to be processed and the priority of the updates to be processed.

In addition to collecting component lanes, the lanes assigned to update objects need to collect pendingLanes of the root node of the Fiber Tree.

root.pendingLanes = 0; root.pendingLanes = root.pendingLanes | SyncLane; / / pendingLanes for 1 root. PendingLanes = root. PendingLanes | DefaultLane; //Copy the code

By using Root’s pendingLanes, we can know if there are any updates in the Fiber Tree that need to be processed and the priority of the updates that need to be processed before coordination begins. If there are no updates in the Fiber tree to handle, i.e. pendingLanes is 0, then workLoopSync/workLoopConcurrent does not need to be started.

WorkLoopSync/workLoopConcurrent Each time workLoopSync works, React selects the lanes with the highest priority from The pendingLanes as the designated lanes-renderlanes. All updates assigned to that Lanes are then processed. With renderLanes, we can tell if a component fiber node needs to be coordinated or if an Update needs to be handled. If renderLanes are SyncLane, Fiber node lanes are DefaultLane, and Update lanes are DefaultLane, this component does not need to be processed in this coordination and can be skipped.

If the lane is Transition or Retry, multiple lanes will be assigned. If not, there is only one lane specified by the coordination. Typically, only one type of Lanes is specified at a time. In special cases, for example, multiple types of Lanes are specified when using useMutableSource. This is called Lanes entangle. This will be explained in more detail in a future article

After understanding Lane, we combine the process of lane and React update as follows:

Here we can explain the previously difficult process of interrupting low-priority updates in Concurrent mode:

That makes it easier to understand.

The allocation strategy for lanes and the update processing strategy for coordination are different depending on the mode.

Lane in Legacy mode

In Legacy mode, updates have no concept of priority. All UPDATE objects are created with a Lane assigned as a SyncLane, regardless of the context in which the update is triggered. This means that all updated fiber node lanes are SyncLane, and root’s pendingLanes are always SyncLane.

Once workLoopSync is working, you can only specify a SyncLane for this coordination and handle all updates to the assigned SyncLane. When the component nodes are coordinated, all the updates collected in the Update Ue are assigned the same lane-Synclane, so they are all iterated to calculate the new state.

In Legacy mode, all updates are assigned the same Lane-synclane, and updates are processed in FIFO order without update interruptions.

Lane in Concurrent mode

In Concurrent mode, updates have the concept of priority. React assigns a lane to each update based on the context in which the update is triggered.

Once workLoopConcurrent starts working, renderLanes are specified for this coordination from root.pengingLanes, and then fiber nodes and update objects that match renderLanes are processed.

Update objects collected in the updateQueue component are assigned different lanes depending on the update context. If lanes are different, renderLanes match and renderLanes do not match. So what do you do when you iterate over update Value?

First, updates in update Ue that do not match renderLanes are skipped.

For example, if update UE is A1 -> B2 -> C1 -> D2 and renderLanes are 1, the first and third updates will be processed, and the second and fourth updates will be skipped.

Second, some updates rely on the return value of the previous update object. To ensure continuity of state, you need to record the position and state of the update when lane was first unmatched with renderLanes at the time of the last coordination.

In the example above, lane does not match renderLanes for the first time here on the second update when coordinating for the first time. Update B2 -> C1 -> D2 The second time, renderLanes is 2 and state is A, so the final output is ABD.

At this point, you might be confused. We expected ABCD, and now ABD, what went wrong?

This happens because there is a step that is missing. For the first coordination, A1 and C1 are processed and their lanes are set to 0, so for the second coordination B2 -> C0 -> D2 are processed. 0 matches any renderLanes, so on the second coordination, C is processed again and the result becomes ABCD, as we expect.

Side effects caused by Concurrent mode

In Concurrent mode, duplicate component coordination and update processing inevitably occur, which may cause some side effects for react applications and require attention in daily development.

Side effects caused by repeated coordination of components

Repeated coordination of components occurs when a high-priority update interrupts coordination that a low-priority update has begun.

Repeat in coordination in the sample components, we define a child component – Component3, components used in the modify componentWillReceiveProps Component3 state. The parent component of Component3 is Component1, and the lane assigned to Component1 is TransitionLane, which is low-priority update. When Component1 is updated, the child component Component3 is also updated, and Lane is also a TransitionLane. Because there are many nodes defined in Component3, the coordination process will take a long time and will be segmented.

In addition, we also define component – Component2, the lane assigned to the update is SyncLane, which is a high priority update.

When we only update Component1 Component3 update together, also trigger a componentWillReceiveProps only when coordination, the state change is normal, results are as follows:

When Component3 is coordinated, we trigger the update of High priority Component2, the coordination of Component3 is interrupted, and it will be coordinated again after the update of high priority. In the whole process, componentWillReceiveProps triggers twice, lead to abnormal state change, the results are as follows:

Except componentWillReceiveProps componentWillMount, componentWillUpdate when use, also can appear abnormal situation such as the above example.

ComponentWillReceiveProps, componentWillMount, componentWillUpdate is triggered when the coordination, within a method can change the state, when the component repeat coordination, improper operation will cause additional side effects, React therefore defines this lifecycle method as unsafe_xxxxx, which should be used with caution in Concurrent mode.

Update side effects of repeated processing

Duplicate update processing occurs when multiple priority updates occur within a component.

In the example state repetition, we change state three times in a row in the handler for Click. The update priority is synclane-synclane-synclane. SyncLane – Transitionlane-synclane. It can be obviously found that when the priority is different, the update result is abnormal, and the effect is as follows:

The exception occurred because the third update was processed twice with different priorities. This is only the superficial reason, but the actual reason is that we update the state in an unreasonable way:

const reducer = (state, action) => { switch(action) { case "INCREMENT": state.count = state.count + 1; return { ... state }; . }Copy the code

Let’s examine why the above code has an exception with a different priority.

The first is the same priority. The initial state, we define as state1 and count as 0. The update process is as follows:

  • The first update increments state1’s count, returning state2;
  • The second update increments state2’s count by one, returning state3;
  • The third update increments state3 count by one, returning state4;

The last state4 returned will be the updated state of the component, with count equal to 3, which is normal.

Next comes the case of different priorities. The update process is as follows:

  • The first update is high priority, to be processed, increment state1’s count to return state2;
  • The second update is of low priority and does not handle skips, so save state2 first.
  • The third update is high priority, to be processed, increment count at state2 by 1, returning state 3; State2 is changed and count is 2.
  • At the end of the first coordination, the return state.count is equal to 2;
  • Second coordination, dealing with low-priority updates;
  • The second update process increments the count of state2 saved from the last coordination, returning state4;
  • The third update is processed again, incrementing state4 count by one to return state5;

The last state5 returned will be the updated state of the component, with count equal to 4, resulting in an exception.

The correct way to update state should be as follows, so there are no side effects:

const reducer = (state, action) => { switch(action) { case "INCREMENT": return { ... state, count: state.count + 1 }; . }Copy the code

Therefore, be careful when updating state in Concurrent mode and avoid writing code that could cause side effects, as it could cause an exception due to repeated update processing.

Write in the last

That’s the end of this article.

Finally, we will make a review of the full text. The main contents of this paper are as follows:

  • How to enable Concurrent mode in REact18, the difference between Concurrent mode and Legacy mode, and the meaning of Concurrent mode;
  • The react update process is summarized through eventLoop, workLoop, workLoopSync/workLoopConcurrent.
  • Use Lane to sort out react updates with different priorities.

In addition, this article is based on 18.0.0-alpha-F6ABF4B40-20211020, which may be somewhat different from the final standard edition. If there is a big difference with the standard version, it will be adjusted according to the standard version of this article (the change should not be too big, 😄).

portal

  • React series 1: How react works

  • React series (2) : Use binary tree middle order traversal to understand the coordination process of fiber tree

Reference documentation

  • Introduction to Concurrent mode (experimental)
  • React Technology – Concurrent Mode
  • React Technology Revealed – React concept
  • Don’t Stop Me Now: How to Use React useTransition() hook
  • react@18: useMutableSource
  • EventLoop
  • Message queues and event loops: How do pages come alive