This is my third article dissecting the React source code. If you haven’t read the previous article, be sure to read the first article to help you better read the source code.

Article related Information

  • React 16.8.6 React 16.8.6 React 16.8.6 React 16.8.6 React 16.8.6 React 16.8.6 React 16.8.6
  • Warm up article
  • Render Process (1)

The content of this article connects with the render process (I), of course, there is no problem with not looking at the previous article, because the content is not strongly related.

Now open up my code and navigate to the reactdom.js file in SRC under the React-dom folder, where we’ll start today.

ReactRoot.prototype.render

In the previous article, we explained that when reactdom.render is executed, the internal first checks if root already exists and creates one if not. In today’s article, we’ll learn what happens when root exists.

You can go to line 592 of the code.

As you can see, the unbatchedUpdates function is called in the above code. This function is important in React.

Everyone knows that multiple setstates executed together do not trigger multiple renders of React.

// While age will change to 3, it will not trigger 3 renders
this.setState({ age: 1 })
this.setState({ age: 2 })
this.setState({ age: 3 })
Copy the code

This is because the three setStates are internally optimized for a single update, the term is batch update, and we’ll see how batch updates are handled internally in the next section.

There is no need for root to batch update, so the unbatchedUpdates function is called to tell the root that no batch update is required internally.

Then determine whether parentComponent exists inside the unbatchedUpdates callback. At this stage we can assume that parentComponent does not exist, because very few people add a context component to root. ParentComponent exists is executed root. Render (children, callback), in this case, the render is ReactRoot prototype. The render.

Inside the Render function we first pull root, which is FiberRoot, if you want to read about FiberRoot in the previous article. We then create an instance of ReactWork, which we don’t need to delve into, to execute all of the callbacks passed into reactdom.render after the component is rendered or updated.

Let’s take a look at what’s inside updateContainer.

We first pull its Fiber object from the FiberRoot current property, and then calculate two times. These two times are important in React, so we’ll learn them in a separate section.

time

First is currentTime, in internal requestCurrentTime function computation time recomputeCurrentRendererTime is the most core functions.

function recomputeCurrentRendererTime() {
  const currentTimeMs = now() - originalStartTimeMs;
  currentRendererTime = msToExpirationTime(currentTimeMs);
}
Copy the code

OriginalStartTimeMs is a variable that is generated when the React application initializes. The value of originalStartTimeMs is also performance.now(). And this value will not be changed later. So when you subtract these two values, you get how much time has passed since the React application was initialized.

Then we need to turn the calculated value by a formula to calculate it again, this role is | 0 is rounded, that is to say, 11/10 | 0 = 1

Now let’s assume some variable values, and it’ll be a little bit easier to understand if you plug them in.

If the originalStartTimeMs is 2500 and the current time is 5000, then the difference is 2500, which means that 2500 milliseconds have passed since the React application was initialized.

currentTime = 1073741822 - ((2500 / 10) | 0) = 1073741572
Copy the code

Context Context = expirationTime = context context = context context = context = context = context Synchronization is the highest priority, with a value of 1073741823, which is the constant MAGIC_NUMBER_OFFSET plus one we saw earlier.

There are a lot of branch in computeExpirationForFiber functions, but the calculation at the heart of the only three lines of code, respectively is:

/ / synchronize
expirationTime = Sync
// Interactive events with a higher priority
expirationTime = computeInteractiveExpiration(currentTime)
// Asynchronous with lower priority
expirationTime = computeAsyncExpiration(currentTime)
Copy the code

Then we have to analyze the internal computeInteractiveExpiration function is how to calculate the time, of course computeAsyncExpiration computing time is the same, just change the two variables.

All of this code is just a formula, and we can just plug in the values and get the result.

time = 1073741822 - ((((1073741822 - 1073741572 + 15) / 10) | 0) + 1) * 10 = 1073741552
Copy the code

In addition, the 1 * bucketSizeMs/UNIT_SIZE in the ceiling function is used to smooth out the time difference over a period of time. No matter how many tasks need to be executed within the time difference, they all have the same expiration time. This is also a performance optimization to help reduce the behavior of the rendered page.

Finally, in fact, we calculate this expirationTime is able to reverse deduce another time:

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

If we calculate the expirationTime before and substitute it into the above code, the result is as follows:

(1073741822 - 1073741552) * 10 = 2700
Copy the code

This is actually close to the 2,500 millisecond difference we calculated above. Because expirationTime refers to the expirationTime of a task, React calculates the expirationTime of a task based on the task priority and current time. React can always delay the task execution as long as the value is larger than the current time in favor of higher-priority tasks, but the task must be executed as soon as the task deadline passes.

This part of the content has been a bit of a headache. Of course, if you’re too bothered, just remember that the expiration time of a task is calculated by adding the current time to a constant (which varies depending on the priority of the task).

In addition, you can see a more intuitive and easy way to calculate the expiration time in the later code, but that part of the code is not used yet.

scheduleRootUpdate

When we calculate the time is called updateContainerAtExpirationTime, this function is no good resolution, we directly into scheduleRootUpdate function.

First we will create an update object that is bound to setState

// Update the internal properties of the object
expirationTime: expirationTime,
tag: UpdateState,
// the first two arguments of setState
payload: null.callback: null.// Used to find the next node in the queue
next: null.nextEffect: null.Copy the code

For properties inside the Update object, it is the next property that we need to focus on. Since an UPDATE is essentially a node in a queue, this property can be used to help us find the next update. For batch updates, we may create multiple updates, so we need to concatenate and store these updates and use them to update state if necessary.

In render, it’s an update, but we don’t have setState, so payload is {element}.

Next we assign callback to the update property, where callback is the third argument to reactdom.render.

We then insert the update object into the queue. EnqueueUpdate has a lot of branches and the code is simple, so we won’t post the code here, if you are interested. The update () function creates or retrieves a queue and adds the update object to the queue.

Finally, the scheduleWork function is called, starting with scheduling, which we will explore in detail in the next article.

conclusion

The above is all the content of this article, the core of this article is actually put on the calculation time, because this time and the following scheduling is closely related, finally through a flow chart to summarize the content of the Render process of two articles.

The last

Reading the source code can be a tedious process, but the benefits can be huge. If you have any questions along the way, please feel free to let me know in the comments section.

Also, writing this series is a time-consuming project, including maintaining code comments, making the article as readable as possible, and finally drawing. If you think the article looks good, please give it a thumbs up.

The next article is again about the Render process.

Finally, feel the content is helpful can pay attention to my public number “front-end really fun”, there will be a lot of good things waiting for you.