hawx1993 / tech-blog

📦My personal tech blog,not regularly update
http://sf.gg/u/trigkit4/articles
339 stars 30 forks source link

In-depth understanding of React Fiber mechanism #33

Open hawx1993 opened 1 year ago

hawx1993 commented 1 year ago

引言

Before getting to know FIber in depth, we can first understand the development history of React architecture:

The React15 architecture can be divided into two layers:

The React16 architecture can be divided into three layers.

Why React Officially Launched Fiber

The Fiber architecture was developed by React to solve some performance problems in React and to provide some new features.

One of the main goals was to solve React's "single-threaded rendering" problem. In React's traditional architecture, the entire rendering process is performed on a single JavaScript thread, which means that if there are many large components in the component tree, then long blocks can occur during the rendering process. This can lead to performance issues and can have a negative impact on the user interface.

The Fiber architecture solves this problem by using co-processing. Co-processing allows the rendering of components to be paused and restarted during the rendering process, which allows higher priority components to be prioritized, thus improving performance.

In addition, the Fiber architecture offers many other new features, including better animation support, better browser event handling, better synchronous and asynchronous rendering support, and better accessibility support.

Off-topic: What is the Fiber architecture'scoroutines

The Fiber architecture uses coroutines to optimize the rendering process of React applications. In the Fiber architecture, each component corresponds to a coroutines, and it is possible to pause and restart the rendering of a component during the rendering process. This allows prioritizing components with higher priority, thus improving performance.

The Fiber architecture's concurrency is implemented by using JavaScript's Generator function

The Generator function is a special function that pauses and saves the current execution state while it is being executed. This makes it possible to pause the function at any time during execution and resume execution at a later time. In the Fiber architecture, each component corresponds to a Generator function that is called during the rendering process and pauses and resumes execution when necessary.

Note that the Fiber architecture's concurrency is not a true multi-threaded concurrency, but rather simulates the behavior of multiple threads on a single JavaScript thread. However, it can achieve multi-thread-like effects without creating new threads, and can improve performance in some cases.

So, what is Fiber?

React Fiber can be thought of as a re-implementation of React's core algorithm. So how do we understand Fiber in React? We can understand it on two levels.

Off-topic: what does a linked list look like

image.png

As shown above, where memoizedState is where we store the hooks data. It is a linked list linked by next.

So, what is Fiber mainly used to solve?

In React15 and before, a recursive approach is used to create the virtual DOM, and the recursive process cannot be interrupted. If the component tree is very deep in the hierarchy, recursion will take up a lot of time in the thread and cause lag.

As we know, the browser assigns single thread to js, and JS and UI threads are mutually exclusive. Whenever a JS thread is executed, the UI thread will be hung and wait for the JS execution to finish before continuing. Therefore, the user will perceive the page lag situation.

Therefore, React Fiber introduces dynamic priority and interruptible rendering

By addressing these issues, React Fiber improves the performance of UI rendering, especially when dealing with large or complex UI. It also introduces many new features to React, such as animation optimization and asynchronous rendering.

So, what is dynamic prioritization?

Dynamic prioritization is a new concept introduced by React Fiber that allows React to adjust the priority of work based on the current state during "reconciliation". This can be done by adjusting the "weight" of each task.

In React Fiber, each task has a weight value that indicates its priority. For example, a task may have a higher weight than another task, which means it will proceed before the latter. The weight value can be positive or negative, and a larger weight value indicates a higher priority.

React Fiber uses these weight values to determine which tasks should be executed earlier, thereby improving performance. For example, if there is a long-running task, it can be set to a lower weight value so that other tasks can be completed first. This allows the browser to respond to user input faster, improving the user experience.

Dynamic prioritization is implemented by React Fiber by tracking the current state and constantly adjusting the priority. It can be controlled through React's "scheduleUpdate" method, which allows developers to set weight values for specific components. This allows developers to more precisely control React's "reconciliation" process to optimize performance.

The previous version of react used the expirationTime property to represent the priority, the priority and IO can not work well together (the priority of io is higher than the priority of the cpu), now there is a more fine-grained priority representation Lane, Lane with a binary bit to represent the priority, the 1 in binary indicates the position, the same binary number can have more than one bit of the same priority, which can represent the concept of 'batch', and the binary is convenient to calculate.

Similar to a racing lane, the closer to the inner circle the shorter the track and the closer to the outer circle the longer the track. react represents 31 tracks by 31 bits of binary, the smaller the number of bits the higher the priority of the track.

So, how does React achieve an interruptible rendering process?

After talking about dynamic priorities, let's dive into what interruptible rendering is and how React achieves interruptible rendering process?

In React Fiber, the rendering process is done in "frames". Each frame is a period of time during which React can perform some work and then return control to the browser. This allows the browser to process user input or perform other tasks during the rendering process, thus avoiding blocking the entire UI.

In each frame, React performs a number of "tasks", such as rendering components, calling lifecycle methods, executing data requests, etc. React determines the order of execution based on the weight value (dynamic priority) of each task.

When React is executing a task, if it finds that the execution of a task is longer than the time remaining in the current frame, it pauses and resumes execution in the next frame. This is how React Fiber implements the pause and resume of the rendering process.

In this way, React can keep the browser responsive during rendering and avoid blocking the entire UI. this can improve the user experience, especially when dealing with large or complex UI.

So, how did Fiber manage to cede control?

In building and updating the user interface, React Fiber processes tasks in batches and yields control between batches so that the browser can perform other actions. This allows React Fiber to perform a large number of tasks without blocking the browser and improves the performance of UI rendering

React Fiber uses the technique of yielding control to improve the performance of UI rendering, but this does not mean it will always yield control. When the browser has ample CPU resources available, React Fiber can continue to execute tasks without yielding control in order to complete the UI rendering as quickly as possible.

In React, the technique of yielding control is typically implemented using the browser's requestIdleCallback function (react has implemented its own version of requestIdleCallback using MessageChannel + requestAnimationFrame).

"This function allows a callback function to be called when the browser is idle and allows specifying how much CPU time can be used at most. React Fiber can use this function to execute tasks during browser idle times, yielding control."

Why did React implement its own version of requestIdleCallback??

Why not setTimeout?

Because if the recursive level of setTimeout is too deep, the delay will not be 1ms but 4ms, which can cause a long delay

Why not requestAnimationFrame?

The requestAnimationFrame is executed after the microtask is executed and before the browser rearranges and redraws, and the timing of the execution is inaccurate. If the execution time of JS before raf is too long, it will still cause a delay.

Compared with setTimeout, the biggest advantage of requestAnimationFrame is that the system determines the timing of the callback function execution. (If the screen refresh rate is 60Hz, then the callback function is executed once every 16.7ms)

Why not requestIdleCallback?

The execution timing of requestIdleCallback is executed after the browser rearranges and redraws, which is the idle time of the browser. In fact, the timing of execution is still inaccurate.

Why MessageChannel?

First, MessageChannel is executed earlier than setTimeout. Second, requestIdleCallback is not supported by all browsers. To solve this problem, React uses MessageChannel to emulate requestIdleCallback.

image.png

image.png

React uses it to emulate the behavior of a requestIdleCallback, such as performing UI updates in the main thread and other compute-intensive tasks in the worker thread.

"In this case, React can perform UI updates on the main thread and non-UI tasks on a worker thread. This avoids blocking the main thread and improves performance."

MessageChannel pr reference:Use setImmediate when available over MessageChannel

Why does Fiber have to be a linked list, can't it be an array?

The reason Fiber uses a linked list data structure is because a linked list allows for easy insertion and removal of elements in the middle of a list. This is useful when building and updating a user interface, as there may be a large number of elements that need to be inserted or removed.

Linked lists have better insertion and deletion performance compared to arrays because performing these operations on arrays typically requires moving a large number of elements, while in linked lists only a few pointers need to be modified.

Disadvantages of linked lists: However, linked lists often have worse search performance compared to arrays because the entire list must be traversed to find the desired element.

Despite this, Fiber still chooses to use a linked list as its data structure because the need for inserting and deleting elements during the building and updating of a user interface is much greater than the need for searching elements.

So, why Fiber can improve performance?

From the above conclusions:

So, what is the difference between Fiber Reconciliation and DOM Diff

"DOM diff" usually refers to operations performed in the browser, while React's "reconciliation" occurs in the application's JavaScript code.

There are several advantages when using React's "reconciliation" for virtual DOM comparisons and updates:

Why React Reconciliation Makes Performance Better?

We mentioned earlier that in React15, the virtual DOM is created through recursion, which is an uninterruptible process. This can lead to performance issues with lag. So, how does React's reconciliation improve performance?

In React, when a component's state changes, the component will be re-rendered. However, sometimes a component's state may change but the component does not actually need to be re-rendered. For example, when a parent component's state changes, it may cause the state of a child component to also change, but if the child component's view has not truly changed, then there is no need to re-render it.

Reconciliation solves this problem by "reconciling" the components. When the state of a parent component changes, reconciliation will first re-render the parent component. It will then "reconcile" the child components to determine which ones actually need to be updated. This way, unnecessary re-renders of components can be avoided, improving performance.

Reconciliation also offers some other optimizations such as "cascading updates," which involves batch updating multiple components in a single re-render. This can reduce the number of times the browser needs to repaint, further improving performance.

Overall, React reconciliation is an effective optimization technique that helps developers improve performance while maintaining the maintainability of the application. It achieves this by reducing unnecessary re-renders of components and optimizing the update process.

Fiber Vs Stack Demo

From the comparison of the above two demos, we can see that Fiber's handling of animation is smoother.

So, how does the Fiber architecture better implement animation support?

React Fiber offers a new way to implement animations called the "requestAnimationFrame" mechanism. This mechanism allows for the processing of animations before the browser's next repaint.

In the traditional architecture of React, animations are implemented using setInterval or setTimeout, which means the frame rate of the animation may not be very stable and it can take up a lot of CPU time.

Instead, the requestAnimationFrame mechanism in the Fiber architecture better controls the frame rate of the animation and doesn't take up as much CPU time. This means that the animation can be smoother and won't have as big of an impact on performance in larger applications.

In addition to improving animation performance, the Fiber architecture also offers a new way to implement animations called "animation scheduling." This mechanism allows developers to have more fine-grained control over the timing of the animation and can make the animation smoother.

What is the difference between the Fiber architecture and React's traditional architecture?

React Fiber is an upgraded version of React's architecture, which has some significant differences from React's traditional architecture:

Although there are many differences between Fiber architecture and traditional architecture, the differences between them are not obvious to developers. In most cases, developers can continue to use React's standard way of developing applications without knowing the underlying implementation details.