Performance has always been an important topic in front-end development. As the front-end can do more and more things, the capabilities of the browser are being amplified and exploited: from Web games to complex single-page applications, from NodeJS services to Web VR/AR and data visualization, front-end engineers are always pushing the envelope. Some of the following performance problems have been solved, and some become insurmountable shield walls.

So what are we talking about when we talk about performance? What are the performance characteristics of applications developed based on React framework?

In this article, we analyze the front-end performance from the perspective of browsers and JavaScript engines, and innovate React to make full use of browser capabilities to break through limitations.


Before I begin, I’d like to introduce a book to you.

Last year, YAN Haijing and I started our co-writing journey. This year, the book React State Management and Isomorphism Combat, which we worked on together, was finally published. The core of this book is the React stack. On the basis of introducing the use of React, it analyzes the idea of Redux from the source level, and focuses on the architecture mode of server-side rendering and homogeneous application. The book contains many project examples that not only open the door to the React stack, but also improve the reader’s overall understanding of the frontier.

If you are interested in the book content or the following content, please support us! More at the end of this article, don’t go away!


The Achilles heel of performance issues

In fact, performance problems can vary: bottlenecks can occur during network transmission, causing front-end data to appear late; It could also be that in hybrid applications, the WebView container is limiting. However, when analyzing performance issues, there is often a concept that cannot be avoided: JavaScript single threads.

** Browsers parse and render DOM trees and CSS trees, parse and execute JavaScript, and almost all operations are performed in the main thread. ** Because JavaScript can manipulate the DOM and affect rendering, JavaScript engine threads and UI threads are mutually exclusive. In other words, JavaScript code execution blocks the rendering of the page.

Take a look at the following illustration:

A few key players in the picture:

Call Stack: The Call Stack, where the JavaScript code executes, in Chrome and NodeJS for the V8 engine. When it has completed all the current tasks, the stack is empty, waiting to receive the next Tick in the Event Loop.

Browser APIs: This is the bridge between JavaScript code and the Browser internals, allowing JavaScript code to manipulate the DOM, call setTimeout, AJAX, etc., using the Browser APIs.

Event Queue: Each time an asynchronous callback is added via AJAX or setTimeout, the callback function is generally added to the Event queue.

Job Queue: This is a higher-priority channel reserved for promises that says “execute this code later, but before the Next Event Loop tick.” It belongs to the ES specification and is not expanded here.

Next Tick: Indicates the task to be executed in the Next Tick. It consists of callbacks in an Event queue, all job queues, and some or all render queues. Note that the current tick enters the next tick only when the Job queue is empty. This involves task priority, which you may be more familiar with with microtasks and Macrotasks, but will not be expanded here.

Event Loop: This will “monitor” (poll) whether the Call Stack is empty. If the Call Stack is empty, the Event Loop will push tasks in the next tick to the Call Stack.

In the main thread of the browser, JavaScript code may call the browser API to manipulate the DOM while executing the call stack. It is also possible to perform asynchronous tasks that would normally be added to the Event queue if handled as callbacks; If it was a promise, it would be put in the Job queue first. These asynchronous and render tasks will be handled by the call stack in the next sequence.

If you call the Call Stack to run a time-consuming script, such as parsing an image, the Call Stack will be jammed like a loop entrance during Beijing rush hour. ** The main thread queues all other tasks, blocking the UI response. At this point, users click, input, page animation, etc., there is no response.

Such performance bottlenecks, like an Achilles heel, limit JavaScript to some extent.

Two – way performance antidote

We generally have two solutions to break the bottleneck mentioned above:

  • Long tasks that are time-consuming, costly, and easy to block are sliced into sub-tasks and executed asynchronously

As a result, these subtasks are executed in different Call Stack tick cycles, so that the main thread can perform UI updates between subtasks. Consider a common scenario: if we need to render a list of 100,000 pieces of data, instead of rendering the entire data at once, we can fragment the data and use the setTimeout API to process it step by step. The work of building the render list is broken up into different sub-tasks to be performed in the browser. In between these subtasks, the browser can handle UI updates.

  • Another innovative approach: using HTML5 Web workers

Web workers allow us to execute JavaScript scripts in different browser threads. Therefore, some time-consuming calculation processes can be handled in the threads started by the Web worker. More on that below.

React Framework performance analysis

Community content on React performance tends to focus on the business level, focusing on “best practices” using the framework. Instead of talking about “shoulComponentUpdate to reduce unnecessary rendering” and “reduce inline-function in render function”, this article mainly analyzes performance bottlenecks and breakthrough strategies from the implementation level of React framework.

There is no dispute that native JavaScript is the most efficient. ** React takes more time in JavaScript execution than other frameworks because:

Virtual DOM build -> calculate DOM diff -> generate render patch

It’s a complex set of processes. That is, up to a point: React’s famous scheduling strategy, Stack Reconcile, is React’s performance bottleneck.

This is not hard to understand because DOM updates are just JavaScript calling the browser APIs, and the process is performed in the same black box as all frameworks and native JavaScript, where the performance cost is the same and inevitable.

In our React: Stack Reconcile procedure, depth-first diff traverses all Virtual DOM nodes. After the whole Virtual DOM is calculated, the task is removed from the stack to release the main thread. Therefore, when the main thread of the browser is occupied by the React status update task, the user does not get any feedback from the browser until the task is completed.

Take a look at a typical scenario from the article: What is React Fiber?

This example creates an input box, a button, and a BlockList component on the page. The BlockList component renders a number of boxes based on the NUMBER_OF_BLOCK value, which displays the number of times the button was clicked.

In this example, we can set NUMBER_OF_BLOCK to 100000. Clicking the button triggers setState and the page starts updating. At this point, click on the input box and enter some string, such as “hi, react”. As you can see, the page has no response. After 7 seconds, “hireact” is displayed in the input box. At the same time, the BlockList component has been updated.

Obviously, this is not a good user experience.

The browser main thread in this 7s performance is shown below:

The yellow part is JavaScript execution time, which is also the React main thread time; In purple, the browser recalculates the DOM Tree; The green part is when the browser draws the page.

These three tasks occupy the main thread of the browser for 7s, during which the browser cannot interact with the user. The React component in yellow takes a long time to execute and takes up 6s. In other words, the React component occupies the main thread for a long time, and the main thread cannot respond to user input. This is a classic example.

React Performance upgrade — React Fiber

The React core team has been aware of performance risks for a long time and continues to explore ways to address them. Based on browser support for the requestIdleCallback and requestAnimationFrame apis, the React team implemented a new scheduling strategy called Fiber Reconcile.

React Fiber — What is React Fiber? In the React Fiber scenario, the previous example is repeated, and the page will not be stuck again. The interaction is natural and smooth.

The performance of the browser’s main thread is shown below:

As you can see, the browser is also recalculating the DOM Tree and redrawing it during the yellow JavaScript execution, when React is occupying the main thread of the browser. As you look at it, yellow and purple alternate, while screenshots of the page show that user input is responding in a timely manner. In short, while React is occupying the browser’s main thread, the browser is also interacting with the user. This is clearly a “better performance” performance.

Here’s the first approach to React: “Segment time-consuming tasks” to achieve a performance breakthrough. Let’s look at another “folk” practice, using Web workers.

React works with Web workers

This article will not repeat the concept of Web worker, you can visit the MDN address to understand. Let’s focus on: If React is connected to Web workers, where do we start and how do we implement it?

As you know, the standard React application consists of two parts:

  • React Core: Handles most complex Virtual DOM calculations;

  • React-dom: Is responsible for interacting with the browser’s real Dom to display content.

The answer is simple. We try to run the React Virtual DOM calculation in the Web worker. Move the React Core part into the Web worker thread.

Someone did come up with this idea, see React Warehouse Issue #3092, which also attracted a discussion from Dan Abramov. Although such a proposal was rejected, it did not prevent us from making React test with worker.

Talk is cheap, Show me the code, and Demo: Use React and Web Worker React to compare their performance. For the code part, interested students can private message me.

Final conclusion: Only when a large number of nodes change, Web worker’s improvement of rendering performance will have some effect. When the number of nodes is very small, the performance of accessing Web worker may be negative. I think this is due to the cost of communication between the worker thread and the main thread.

There is still room for performance improvements in the Web Worker version of React.

  • Since the performance cost of worker thread and main thread using postMessage communication is high, we can adopt batching to reduce The Times of communication.

It’s not particularly wise to call postMessage to notify the main thread every time the DOM needs to change. Therefore, the batching idea can be used to collect the DOM content to be updated calculated from worker threads and then send it uniformly. So the granularity of batching is interesting. If we go to extremes and each batching collects so many changes that we don’t send them to the main thread, then one batching stresses the browser’s actual rendering process, which is counterproductive.

  • Transferable Objects is used to load data when sending messages using postMessage
  • About syntheticEvent for Worker

Native React has an event system that listens for all browser events at the top level and then converts them into synthetic events, which are passed to the event listeners we define on the Virtual DOM.

For our Web worker, since the worker thread cannot directly manipulate the DOM, it cannot listen for browser events. Therefore, all events are also processed in the main thread, converted into virtual events and then transmitted to the worker thread for publication, which means that all operations related to the creation of virtual events are still carried out in the main thread. A possible solution to improve this is to directly transmit the original events to the worker. The worker generates mock events and bubbles them.

There are a lot of things to learn about React and worker, such as the synchronization of preventDefault and stopPropogation in event processing (the worker thread and main thread communicate asynchronously). Multiple worker (more than one worker) is used for exploration, etc. If readers are interested, I will write a special article about it.

Story and Web worker

Since React can access Web workers, Redux can certainly learn from this idea and put the complicated pure calculation process of Reducer in Redux into the worker thread. Is it a good idea?

I use the “N-Queen problem” to simulate large calculations. In addition to this extremely time-consuming algorithm, the page also runs several modules to implement the rendering logic for frequently updating the DOM:

  • A Blinker module that displays a count (increments by 1 per second) every 16 milliseconds in real time;

  • A counter module that updates the background color every 500 milliseconds;

  • A slider module with permanent reciprocating motion;

  • A spinner that flips 5 degrees every 16 milliseconds

As shown in figure:

These modules update the DOM style regularly and frequently for rendering. Normally, these renderings would get stuck during n-queen calculations in the JavaScript main thread.

If we put the N-queen calculation on the worker thread, we can see that the demo shows a surprising performance improvement, completely silky and without lag. As shown in the figure above, the left part is the normal version, and there is no unexpected page lag. The right part is the application after worker is connected.

At the implementation level, abstract encapsulation is completed with the help of Enchancer design of Redux library. A Store enhancer is essentially a curried higher-order function, which is similar to the high-order component concept in React, as well as the more familiar middleware. The result of Redux’s applyMiddleware method is a Store Enhancer.

So why not use enhancer instead of middleware? The common library design idea used in the Redux worker demo is very interesting. The advanced content of the magical Redux is no longer available, but can be found in my new book. Which brings us to the commercial break…


React State Management and Isomer Combat was jointly polished by Yan Haijing, a well-known technology guru in the front end, and condensed our accumulation and experience in the process of learning and practicing React framework. ** In addition to the React framework usage introduction, focuses on the analysis of state management and server rendering isomorphism application content. ** has also absorbed a large number of excellent ideas from the community and summarized and compared.

The book was written by Baidu vice President Shen Dou, Baidu senior front end engineer Dong Rui, Ruan Yifeng, a well-known expert on JavaScript language, Wolf Shu, a nojue advocate, JustJavac, the founder of Flarum Chinese community, Xiaojue, a technical expert on sina mobile, gu Yiling, a senior front-end engineer of Baidu, and other experts in the front-end area of Jue.

Interested readers can click here for details. You can also scan the QR code below to buy. Thank you again for your support and encouragement! Please criticize and correct!

Finally, front-end learning is endless, I hope to make progress together with every technology enthusiast, you can find me in Zhihu!

Happy coding!