React 18 brings several very useful new features, and at the same time there is no additional upgrade cost, it is worth taking a closer look.
Here are a few key messages:
- React 18 working group . Use the community to discuss the release cadence and new features of React 18.
- release plan . There is no official release yet, but the
@alpha
version is already available, install the alpha version . - React 18 introduces the new features of . Although it has not yet been officially released, the feature introduction can go ahead. This week's intensive reading is mainly to interpret this document.
intensive reading
In general, React 18 brings 3 new features:
- Automatic batching。
- Concurrent APIS。
- SSR for Suspense。
At the same time, in order to open new features, a simple render
function upgrade is required.
Automatic batching
Batching means that React can combine multiple setState
events in the callback function into one rendering.
That is to say, setState
does not modify the State in real time, but combining multiple setState
calls to trigger only one rendering, which can reduce the instability caused by the intermediate value of the program data state and improve the rendering performance. It can be understood as the following code:
function handleClick() {
setCount((c) => c + 1);
setFlag((f) => !f);
// 仅触发一次渲染
}
setState
is executed in the asynchronous call of the callback function, because the context is lost and the merge process cannot be performed, each setState
call will immediately trigger a re-rendering:
function handleClick() {
// React 18 以前的版本
fetch(/*...*/).then(() => {
setCount((c) => c + 1); // 立刻重渲染
setFlag((f) => !f); // 立刻重渲染
});
}
The optimization brought by React 18 is that any situation can be combined and rendered! Even in promise
, timeout
or event
multiple calls to callback setState
, also will be merged into a rendering:
function handleClick() {
// React 18+
fetch(/*...*/).then(() => {
setCount((c) => c + 1);
setFlag((f) => !f);
// 仅触发一次渲染
});
}
Of course, if you have to re-render immediately after calling setState
, you only need to wrap it flushSync
function handleClick() {
// React 18+
fetch(/*...*/).then(() => {
ReactDOM.flushSync(() => {
setCount((c) => c + 1); // 立刻重渲染
setFlag((f) => !f); // 立刻重渲染
});
});
}
The premise of enabling this feature is to replace ReactDOM.render
ReactDOM.createRoot
invocation mode.
New ReactDOM Render API
The upgrade method is very simple:
const container = document.getElementById("app");
// 旧 render API
ReactDOM.render(<App tab="home" />, container);
// 新 createRoot API
const root = ReactDOM.createRoot(container);
root.render(<App tab="home" />);
The main reason for API modification is semantics, that is, when we call render
multiple times, we no longer need to repeatedly pass in the container
parameter, because in the new API, container
has been bound to root
in advance.
ReactDOM.hydrate
has also been replaced ReactDOM.hydrateRoot
const root = ReactDOM.hydrateRoot(container, <App tab="home" />);
// 注意这里不用调用 root.render()
The advantage of this is that if we call root.render(<Appx />)
root
, we don't need to care that this 060ebda72f2139 comes from createRoot
or hydrateRoot
, because the subsequent API behavior is the same, which reduces the cost of understanding.
Concurrent APIS
First, we must understand what Concurrent Mode is.
Simply put, Concurrent Mode is a design architecture that can interrupt rendering. When will the rendering be interrupted? When a higher priority rendering arrives, by abandoning the current rendering, the higher priority rendering is executed immediately, in exchange for a visually faster response speed.
Someone might say that it’s not right. After the rendering is interrupted, the CPU execution of the previous rendering is wasted. In other words, the overall execution often increases. This sentence is correct, but in fact, users' perception of the timeliness of page interaction is divided into two types, the first is immediate input feedback, and the second is the side-effect feedback caused by this input, such as updating the list. Among them, even if the input feedback can be satisfied first, even if the side-effect feedback is slower, it will bring a better experience, not to mention that most of the side-effect feedback will be invalidated even if the input feedback changes.
Because React changes the rendering DOM tree mechanism to two doubly linked lists, and there is only one rendering tree pointer, which points to one of the linked lists, you can switch the pointer point after the update has completely occurred, and you can give up on the other at any time before the pointer is switched. Modification of the tree.
The above is the background input. React 18 provides three new APIs to support this mode, namely:
- startTransition。
- useDeferredValue。
- <SuspenseList>。
The latter two documents have not been released yet, so this article only introduces the first API: startTransition. First look at the usage:
import { startTransition } from "react";
// 紧急更新:
setInputValue(input);
// 标记回调函数内的更新为非紧急更新:
startTransition(() => {
setSearchQuery(input);
});
In simple terms, it is to be startTransition
wrapped callback setState
rendered triggered is marked as urgent rendering, render these may be preempted by other emergency rendering.
For example, in this example, when setSearchQuery
Many updated list of contents, resulting in 100% CPU utilization when rendering, then the user has made an input that is triggered by setInputValue
rendering caused this time by the setSearchQuery
triggered rendering will stop immediately, Instead setInputValue
rendering, so that user input can be quickly reflected on the UI, at the cost of a slightly slower search list response. The transition
can be accessed through isPending
:
import { useTransition } from "react";
const [isPending, startTransition] = useTransition();
In fact, this is more in line with the design concept of the operating system. We know that the operating system responds to the underlying hardware events through interrupts, and the interrupts are very urgent (because the hardware can store the message queue is very limited, the operating system cannot respond even if the hardware input may be Lost), so it is necessary to support the preemptive kernel, and execute the interrupt immediately when the interrupt arrives (may put the less urgent operation in the lower half of the execution).
For front-end interaction, the "interruption" from the user's perspective generally comes from keyboard or mouse operations, but unfortunately, the front-end framework and even JS are too high-level, and they cannot be automatically recognized:
- Which codes are generated by the emergency interrupt. For example,
onClick
must be caused by the user's mouse click? Not necessarily, it may bexxx.onClick
, not by the user. - Must the user trigger an emergency interrupt? Not necessarily, such as the keyboard input,
setInputValue
emergency, and update the list of queriessetSearchQuery
is non-urgent.
We need to understand the limitations of the front-end scenes to the user's operation perception, in order to understand why the urgency of the update must be manually specified, instead of the operating system, the upper-level program does not need to perceive the existence of interruption.
SSR for Suspense
The full name is: Streaming SSR with selective hydration.
That is, like water flow, create a continuous rendering pipeline from the server to the client, instead of a one-time rendering mechanism like renderToString
Selective hydration means selective hydration. Hydration means that after the back-end content is hit to the front-end, JS needs to bind events to it in order to respond to user interaction or DOM update behavior. Before React 18, this operation must be integrated. , And the hydration process may be slower, which will cause global stagnation, so selective hydration can give priority to hydration as needed.
So this feature is actually prepared for SSR, and the function enablement carrier is Suspense (so don't think that Suspense is just a loading function in the future). In fact, at the beginning of the design of Suspense, it was to solve the server-side rendering problem. Only the on-demand loading function of the client-side test was installed at the beginning. Later, you will gradually find that the React group gradually gives Suspense more powerful capabilities.
SSR for Suspense solves three main problems:
- In SSR mode, if different modules have different fetching efficiencies, the slowest module will slow down the overall HTML throughput time, which may cause the experience to be not as good as non-SSR. As an extreme case, suppose a component in the report relies on slow query and takes five minutes of data to come out, then the consequence of SSR is that the white screen time is prolonged to 5 minutes.
- Even if the SSR content hits the page, since the JS has not been loaded, hydration is not possible at all, and the entire page is in a state of inability to interact.
- Even if the JS is loaded, because React 18 can only perform overall hydration before, it may cause a freeze, resulting in untimely response to the first interaction.
In the server render of React 18, just use pipeToNodeWritable
instead of renderToString
and cooperate with Suspense
to solve the above three problems.
Use pipeToNodeWriteable
to see this example .
The biggest difference is that the server-side rendering is changed res.send
res.socket
, so that the rendering changes from a single behavior to a continuous behavior.
So what is the effect of React 18's SSR? This introductory document is recommended to take a look. It is very intuitive. Here I will briefly describe it:
- The block wrapped by
<Suspense>
will not block the first throughput when rendering on the server, and after the block is prepared (including asynchronous fetching), it will be printed to the page in real time (in HTML mode, there is no hydration at this time), Before that, the contentfallback
- The process of hydration is also gradual, so that it will not cause the page to freeze after executing all the complete js (hydration is actually the callback registration and various Hooks written in React, and the amount of the entire application is very large).
- Because hydration is split into multiple parts, React will also monitor mouse clicks in advance, and hydration the priority of the clicked area in advance, and can even preempt hydration that is already in progress in other areas.
So to sum it up, the secret of the performance improvement of the new version of SSR lies in two words: on-demand.
The difficulty is that SSR requires back-end to front-end cooperation. Before React 18, the back-end to front-end process was not optimized at all. Now, the throughput of SSR HTML is changed to multiple times, as needed, and support during the hydration process. Preemption, so performance is further improved.
to sum up
Taken together, React 18 focuses on faster performance and user interaction response efficiency, and its design concept includes the concepts of interruption and preemption.
When we mention front-end performance optimization in the future, we will have more application-side perspectives (not just engineering perspectives), and effectively improve the interactive feedback speed from the following two application optimization perspectives:
- The framework design is interrupted at any time, the first priority is to render the UI interaction module that users pay most attention to.
- The "smooth" pipeline SSR from the back end to the front end, and the hydration process is on-demand, and supports the interruption of higher-priority user interaction behavior, and the first priority is to hydrate the part that the user is interacting with.
The discussion address is: Intensive Reading of "React 18" · Issue #336 · dt-fe/weekly
If you want to participate in the discussion, please click here , there are new topics every week, weekend or Monday release. Front-end intensive reading-to help you filter reliable content.
Follow front-end intensive reading WeChat public
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
Copyright notice: Freely reproduced-non-commercial-non-derivative-keep the signature ( Creative Commons 3.0 License )
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。