Currently, the React Server Component is still under development and research, so it is not suitable for production. But the concept is interesting enough for technologists to learn.

At present, in addition to the interpretation of various domestic blogs and Zhihu, the most first-hand learning materials are as follows:

  1. Dan’s Server Component introduction video
  2. Server Component RFC draft

I will systematically explain the concept of React Server Component and my understanding of the React Server Component by combining these first-hand information with some famous interpretations in the industry.

First, let’s see why the Server Component concept is needed:

The concept of Server Component was developed to solve the impossible triangle of user experience, maintainability, and performance. The impossible triangle is two at most, but not all three.

To briefly explain, user experience is in pages that are more responsive, maintainability is in code that should be cohesive and less coupled, and performance is in request speed.

  • Ensure user experience, maintainability, pull all data with one request, all components render at once. However, when the number of modules continues to increase, useless module information can not be deleted at will, the request will become larger and more redundant, resulting in the bottleneck stuck in the number, that is, the performance is not good.
  • To ensure user experience and performance, consider the parallel fetch. After that, the process will remain unchanged. If a module is added or reduced in the future business logic, we will modify the parallel fetch public logic and the corresponding business module at the same time, which is not maintainable.
  • To ensure maintainability and performance, each module can take the number independently, but in the case of rendering child elements only after the parent rendering, the father and child take the number becomes serial, page loading is blocked, and user experience is not good.

In a nutshell, in a decoupled front-end mode, the only bridge that connects is the fetch request. Have to do user experience well, access will be initiated in parallel, and front-end module is independent of maintenance, do so on the front end access aggregation in this matter, necessarily will destroy the front-end maintainability, and the parallel on the backend, this event will be for the backend cannot parse the front-end module, lead to the aggregate information lag, given over time become redundant.

To solve this problem, it is necessary to deepen the connection between the front end and the back end. Therefore, front-end and back-end conventions like GraphQL are feasible, but it is difficult to promote in the back end because of its high deployment cost and benefits only in the front end.

Server Component is another solution. It starts a Node service to assist the front-end, but instead of API docking, it runs the front-end isomorphic JS code, directly parses the front-end rendering module, automatically extracts requests from it and communicates directly with the Server on the Node side. Because the cost of communication between servers is very low, the front-end code does not need to be adjusted, and the request data is dynamically aggregated on demand, the three problems of “user experience, maintainability, and performance” are solved simultaneously.

Its core improvement points are shown in the figure below:

As shown in the figure above, this is the normal interaction mode of the front and back ends. It can be seen that Root and Child send two requests in sequence. Since network time and serial are both severely blocked parts, they are marked with red lines.

The Server Component can be understood as the following figure, which not only reduces the network loss, but also makes the request parallel, and the return result of the request changes from pure data to a special structure that describes both the UI DSL and the data:

Congratulations on your understanding of the core concepts of Server Component. If you just want to get the basics right, this is the end of the reading. If you’d like to learn more about the implementation details, read on.

An overview of the

In a nutshell, Server Component solves the impossible triangle problem by giving components the ability to render on the Server side. Because of this feature, the Server Component has several striking features that pure client components do not:

  • Components running on the Server side return ONLY DSL information and contain no other dependencies, so all dependent NPM packages of the Server Component are not packaged to the client.
  • With access to any API on the server, you can theoretically do anything in the front-end component that the server can do with Nodejs.
  • The Server Component integrates seamlessly with the Client Component, and the Client Component can be called seamlessly from the Server Component.
  • The Server Component returns information on demand, and under the current logic, any references to branch logic that cannot be reached are not imported by the client. For example, if the Server Component references a huge NPM package, but some branch does not use the function provided by the package, the client will not download the huge NPM package to the local.
  • Because what is returned is not HTML but a DSL, the State generated by the server component is maintained even if it is pulled again. For example, if A is ServerComponent and its child B is Client Component, the state of Component B is modified. For example, if A is triggered to pull the DSL again, the text entered by Component B will be retained.
  • Suspense can be seamlessly integrated into Suspense, and Suspense loading won’t be displayed in time due to network reasons.
  • Shared components can run on both the server and client sides.

Three components

Server Component Components are divided into three types: Server Component, Client Component, and Shared Component, ending with.server.js,.client.js, and.js respectively.

Where.client.js is the same as a normal component, but both.server.js and.js may run on the server side, where:

  • .server.jsIt must be executed on the server.
  • .jsWhere to execute depends on who calls it, and if so.server.jsThe call is executed on the server, if yes.client.jsThe invocation is performed on the client side, so it is subject to the constraints of the server component by nature.

Here is an example of the Server Component shown in the RFC:

// Note.server.js - Server Component
import db from 'db.server'; 
// (A1) We import from NoteEditor.client.js - a Client Component.
import NoteEditor from 'NoteEditor.client';

function Note(props) {
  const {id, isEditing} = props;
  // (B) Can directly access server data sources during render, e.g. databases
  const note = db.posts.get(id);
  
  return (
    <div>
      <h1>{note.title}</h1>
      <section>{note.body}</section>
      {/* (A2) Dynamically render the editor only if necessary */}
      {isEditing 
        ? <NoteEditor note={note} />
        : null
      }
    </div>
  );
}
Copy the code

As you can see, this is the Node/React hybrid syntax. Server components have harsh constraints: they cannot be stateful, and props must be serialized.

JSX can be serialized, and props must follow this rule as well. In addition, the server cannot store state for the client, so the server component cannot use any state-related apis such as useState.

We can get around both of these problems by converting the state to the props entry of the component, which is stored by.client.js, as shown below:

Or by taking advantage of the Server Component’s ability to seamlessly integrate with the Client Component, you can put both the state and the props parameters that can’t be serialized in the Client Component and call them from the Server Component.

advantages

Zero client volume

This may sound like an exaggeration, but it is true under the Server Component constraint. Look at the following code:

// NoteWithMarkdown.js
import marked from 'marked'; / / 35.9 K (gzipped 11.2 K)
import sanitizeHtml from 'sanitize-html'; / / 206 K (gzipped 63.3 K)

function NoteWithMarkdown({text}) {
  const html = sanitizeHtml(marked(text));
  return (/* render */);
}
Copy the code

Marked neither sanitize-html will be downloaded locally, so if this is the only file transfer, the theoretical increase in the size of the client is the string size after the render function is serialized, probably less than 1KB.

Of course, there are limitations behind this. First, the component has no state and cannot be executed in real time on the client side, and running it on the server side may consume additional computing resources if some NPM packages are computationally complex.

The marked package is only read into memory once on the server, and the marked API is executed on the server and returned to the client whenever the backend client wants to use it, rather than the client downloading the marked package.

Full server capability

Since the Server Component executes on the Server side, any Nodejs code can be executed.

// Note.server.js - Server Component
import fs from 'react-fs';

function Note({id}) {
  const note = JSON.parse(fs.readFile(`${id}.json`));
  return <NoteWithMarkdown note={note} />;
}
Copy the code

We can take the understanding of request to a higher level, that is, request is just an Http request initiated by the client, its essence is to access a resource, on the server is an IO behavior. For IO, we can also write and delete resources through the file file system, db can directly access the database through SQL syntax, or Request can directly make requests locally on the server.

Run time Code Split

We all know that Webpack can do static analysis to remove unused imports from the package, while Server Component can dynamically analyze at run time to remove unused imports from the package under the current branch logic:

// PhotoRenderer.js
import React from 'react';

// one of these will start loading *once rendered and streamed to the client*:
import OldPhotoRenderer from './OldPhotoRenderer.client.js';
import NewPhotoRenderer from './NewPhotoRenderer.client.js';

function Photo(props) {
  // Switch on feature flags, logged in/out, type of content, etc:
  if (props.useNewPhotoRenderer) {
    return <NewPhotoRenderer {. props} / >;
  } else {
    return <OldPhotoRenderer {. props} / >; }}Copy the code

This is because the Server Component is pre-packaged when it is built, and the runtime is a dynamic package dispenser. It is perfectly possible to tell which branch logic it is running on and which branch logic it is not using the current running state, such as props. XXX. And only tells the client to pull the missing package of the branch logic currently running into.

Pure front-end mode is written like this:

const OldPhotoRenderer = React.lazy(() = > import('./OldPhotoRenderer.js'));
const NewPhotoRenderer = React.lazy(() = > import('./NewPhotoRenderer.js'));
Copy the code

However, this approach is not native enough, and in real scenarios only front-end frameworks automatically pack routes with a layer of Lazy Load, which is rarely seen in normal code.

None Data fetch of the client round trip

In general, considering the fetch network consumption, we tend to treat it as asynchronous and then show Loading before the data is returned:

// Note.js

function Note(props) {
  const [note, setNote] = useState(null);
  useEffect(() = > {
    // NOTE: loads *after* rendering, triggering waterfalls in children
    fetchNote(props.id).then(noteData= > {
      setNote(noteData);
    });
  }, [props.id]);
  if (note == null) {
    return "Loading";
  } else {
    return (/* render note here... * /); }}Copy the code

This is because in single-page mode, we can quickly fetch the DOM structure from the CDN, but if we wait to fetch, the overall rendering will be slow. The Server Component, on the other hand, executes on the Server side, so it can fetch DOM structures and numbers at the same time:

// Note.server.js - Server Component

function Note(props) {
  // NOTE: loads *during* render, w low-latency data access on the server
  const note = db.notes.get(props.id);
  if (note == null) {
    // handle missing note
  }
  return (/* render note here... * /);
}
Copy the code

Of course, this premise is the network consumption sensitive situation, if itself is a slow SQL query, take a few seconds, this will be counterproductive.

Reducing Component levels

Look at the following example:

// Note.server.js
/ /... imports...

function Note({id}) {
  const note = db.notes.get(id);
  return <NoteWithMarkdown note={note} />;
}

// NoteWithMarkdown.server.js
/ /... imports...

function NoteWithMarkdown({note}) {
  const html = sanitizeHtml(marked(note.text));
  return <div . />;
}

// client sees:<div> <! -- markdown output here --> </div>Copy the code

Note and NoteWithMarkdown are abstracted at the Component level, but the real DOM content entity has only one simple div, so in Server Component mode, the returned content is reduced to that div. Without having to include the two abstract components.

limit

In Server Component mode, there are three components: Server Component, Client Component, and Shared Component, each of which has some limitations as follows:

The Server Component:

  • ❌ can’t useuseState,useReducerEtc state storage API.
  • ❌ can’t useuseEffectAnd other lifecycle apis.
  • ❌ can’t usewindowAnd browser-only apis.
  • ❌ cannot use custom Hooks that contain the above.
  • ✅ Provides seamless access to server data and apis.
  • ✅ can render other Server/Client Components

The Client Component:

  • ❌ cannot reference the Server Component.
    • ✅ But there can be cases in the Server Component where the Client Component calls the Server Component, for example<ClientTabBar><ServerTabContent /></ClientTabBar>.
  • ❌ cannot call server API to get data.
  • ✅ can use everything React with the full capability of the browser.

Shared Component:

  • ❌ can’t useuseState,useReducerEtc state storage API.
  • ❌ can’t useuseEffectAnd other lifecycle apis.
  • ❌ can’t usewindowAnd browser-only apis.
  • ❌ cannot use custom Hooks that contain the above.
  • ❌ cannot reference the Server Component.
  • ❌ cannot call server API to get data.
  • ✅ can be used on both the server and client.

Since Shared components are used on both the server and the client, the benefits of both are greater reusability.

Intensive reading

I think the best and fastest way to quickly understand the Server Component is to find out how it differs from PHP + HTML 10 years ago. Look at the following code:

$link = mysqli_connect('localhost'.'root'.'root');
mysql_select_db('test'.$link);
$result = mysql_query('select * from table');

while($row=mysql_fetch_assoc($result)) {echo "<span>".$row["id"]."</span>";
}
Copy the code

PHP has long been a “Server Component” solution, accessing DB directly on the Server and returning DOM fragments to the client.

The React Server Component, after all this time, finds that the biggest difference is that the HTML fragment returned is a DSL structure, which is actually backed up by a powerful React framework on the browser side. The benefit of this is not only that we can continue to write React syntax on the server rather than degenerate to “PHP syntax “, but also that component state is maintained.

Another important difference is that PHP can’t parse any NPM packages in the current front-end ecosystem, so it can’t parse modular front-end code, so while PHP is intuitively as efficient as Server Component, But the underlying cost is to write another set of syntax that does not rely on any NPM package, JSX, to return HTML fragments, most of the features of the Server Component are not available, and the code is not reusable.

So, in essence, HTML is too simple to adapt to the complexity of today’s front-end, while the common backend framework, although powerful back-end capabilities, is still stuck in the front-end capabilities of 20 years ago (directly return to DOM), only Node middle layer scheme as a bridge, can better connect the modern back-end code and modern front-end code.

PHP VS Server Component

In the PHP era, you could modularize both the front and back ends. Back-end modularity is obvious because the development of back-end code can be modularized and packaged to run on the server. The front end can also be modularized on the server side, as long as we strip out the front and back ends of the code. Here is the back end in blue and the front end in red:

But that there is a problem, because the back-end services are stateless for browser, so the back-end modular itself to conform to its functional characteristics, but the front page is displayed in the user’s browser, by routing to jump to a new page each time, obviously not maximizing the advantage of the client continues to run, we hope to keep the front end, on the basis of modular, There is a continuously running framework on the browser side to optimize the user experience, so the Server Component actually does the following:

There are two main benefits to doing this:

  1. Taking into account the advantages of PHP mode, where the front and back end code seamlessly blends together, brings a range of experiences and enhancements.
  2. The front and back ends are still written in a modular way. The red part in the figure is packaged with the front-end project, so the development retains the modular feature and the React modern framework runs in the browser. Features such as single-page and data-driven can still be used.

conclusion

The Server Component hasn’t matured yet, but the idea is sound.

In order to achieve “user experience, maintainability, performance” at the same time, it is not feasible to focus on the back end or the front end, but to achieve a balance between the front end and the back end. The Server Component expresses a career philosophy that in the future, the front and back ends will still go full stack, where both the front and back ends are made deep at the same time, allowing application development to reach heights that pure front-end or pure back-end cannot reach.

In 2021, the domestic development environment is still relatively backward. The so-called full stack often refers to “knowing a little bit about both the front and back ends”, and it is difficult to hatch the concept of Server Component because each end is not deep enough. Of course, this is also the motivation for us to continue to learn from the world.

Perhaps the difference between PHP and Server Component is the litmus test for whether a person is truly full stack or not. Ask your colleagues!

Read React Server Component · Issue #311 · dT-fe /weekly

If you’d like to participate in the discussion, pleaseClick here to, with a new theme every week, released on weekends or Mondays. Front end Intensive Reading – Helps you filter the right content.

Pay attention to the front end of intensive reading wechat public account

Copyright Notice: Freely reproduced – Non-commercial – Non-derivative – Remain signed (Creative Commons 3.0 License)