Server side rendering in the long period of JSP, PHP, is already in use, but in the case of a single page application is popular, but still has a variety of solutions to support, because the service side rendering do have a lot of a lot of benefits, especially for the Node and three framework combining the front end of isomorphism, use a set of code, front and back side It combines the convenience of single-page application with the benefits of server-side rendering. Here is a look at the principle and process of React Server Render.

The React isomorphism

Key elements of React isomorphism

DOM consistency

Rendering the same Component on the front and back ends will output a consistent Dom structure.

Perfect Component properties and lifecycle and client render timing are key to React isomorphism.

React’s virtual DOM is stored in memory as a tree of objects and can be generated in any javascrT-BASED environment, so it can be generated in the browser and Node. This front and back isomorphism provides a prerequisite.

As shown above:

  1. The React virtual DOM can be generated in any Javascript supported environment, so it can be generated in the browser’s Node environment.
  2. The virtual DOM can be directly converted to a String.
  3. Then it is inserted into an HTML file and output to the browser.

The virtual Dom exists in the form of an object tree on both the front and back ends, but the way the prototype is displayed is different.

  1. In the browser, React uses ReactDom’s render method to render the virtual Dom to the real Dom tree to generate web pages
  2. However, there is no rendering engine in Node, so React provides two other methods:ReactDOMServer.renderToStringReactDOMServer.renderToStaticMarkupYou can render it as an HTML string

Different life cycles

On the server, the Component lifecycle only goes to componentWillMount; the client is complete.

Client render timing

In isomorphism, the server renders the Component with the data as a complete HTML string and returns the data state to the client, which determines whether it can be used directly or needs to be remounted.

These are the basic conditions that React provides for isomorphic/server rendering. In the actual project application, we also need to consider other corner problems, such as the server side does not have window object, need to do different processing.

RenderToString and renderToStaticMarkup

ReactDOMServer provides methods for renderToString and renderToStaticMarkup, using renderToString in most cases, which adds a checksum to the component

React determines whether the client needs to rerender the same by checksum, then it does not rerender, omits the process of creating the DOM and mounting the DOM, then triggers events such as componentDidMount to deal with the pending matters on the server (event binding, etc.), Thus speeding up the interaction time; If not, the component will be remounted to Render on the client.

RenderToStaticMarkup does not generate react related data-*, and there is no checksum. The output HTML is as follows

The component is remounted on the client side, and client remount does not generate checknum(nor is it necessary), so this method is only used when the component rendered on the server is not needed by the client.

Checknum is actually the ADler32 algorithm value for the HTML fragment, which actually calls React.render(
, container); I did the following:

  • Take a look atcontainerIs it null? If it is not null, it is considered that the result is straight.
  • And then whether the first element hasdata-react-checksumIf the value is the same as that obtained by the browser’s AdLER32 algorithm and is compared to the data-checksum, if the value is the same as that obtained by the browser’s Adler32 algorithm, it is not required to render, otherwise it is re-rendered.
var MOD = 65521;

// This is a clean-room implementation of adler32 designed for detecting
// if markup is not what we expect it to be. It does not need to be
// cryptographically strong, only reasonably good at detecting if markup
// generated on the server is different than that on the client.
function adler32(data) {
  var a = 1;
  var b = 0;
  for (var i = 0; i < data.length; i++) {
    a = (a + data.charCodeAt(i)) % MOD;
    b = (b + a) % MOD;
  }
  return a | (b << 16);
}
Copy the code

Matters needing attention

  1. Data status on the server is synchronized to the client

The data generated on the server needs to be returned along with the page, and the client uses this data to render to keep the state consistent. If renderToString is used on the server and the component is still remounted on the client, it is mostly because the HTML is not returned with renderToString on the server, or the format of the returned data is not correct, so you can pay attention to the prompt on Chrome during development

  1. The server needs to pull the data in advance, and the client needs to call the platform difference on componentDidMount. The server rendering only executes on compnentWillMount, so to achieve isomism, you can write the pull logic to the React Class static method. On the one hand, the server can directly manipulate the static method to pull data ahead of time and then generate HTML from the data, on the other hand, the client can call the static method to pull data when componentDidMount

  2. Keep the data deterministic here refers to the data that affects the result of the component render. For example, the following component will be rerendered by the client due to different random numbers generated on the component between the server and client.

Class Wrapper extends Component {
  render() {
    return (<h1>{Math.random()}</h1>); }};Copy the code

You can wrap math.random () into the Component props, generate a random number on the server, and pass it to the Component to ensure that the random number is consistent on both the client and server. Such as

Class Wrapper extends Component {
  render() {
    return (<h1>{this.props.randomNum}</h1>); }};Copy the code

RandomNum is passed to the server

let randomNum = Math.random()
var html = ReacDOMServer.renderToString(<Wrapper randomNum={randomNum} />);
Copy the code
  1. Platform to distinguish

When the current backend shares a set of code, such as the front-end specific window object, Ajax requests cannot be used in the back end. The back end needs to remove the front-end specific object logic or use a corresponding back end solution. For example, the back end can use HTTP. Therefore, it is necessary to distinguish platforms in the following ways

1. The code uses the front and back end common module, such as isomorphic-fetch 2. The front and back ends use Webpack to configure resolve.alias to correspond to different files. For example, the client uses /browser/request.js to make Ajax requests

resolve: {
    alias: {
        'request': path.join(pathConfig.src, '/browser/request'),}}Copy the code

Server webPack uses /server/request.js to replace HTTP

resolve: {
    alias: {
        'request': path.join(pathConfig.src, '/server/request'),}}Copy the code

3. Use webpack.definePlugin to add a platform-specific value at build time. This way, after compiling WebPack UglifyJsPlugin, code that is not on the current platform (unreachable code) will be removed without increasing the file size. For example, add the following configuration to webPack on the server side

new webpack.DefinePlugin({
    "__ISOMORPHIC__": true
}),
Copy the code

Do judgment in JS logic

if(__ISOMORPHIC__){
    // do server thing
} else {
    // do browser thing
}
Copy the code

4. Windows are browser-specific objects, so they can also be used as platform differentiators

var isNode = typeof window= = ='undefined';
if (isNode) {
    // do server thing
} else {
    // do browser thing
}
Copy the code
  1. componentWillReceivePropsIn, the method of relying on data changes should be considered incomponentDidMountBe compatible with

For example, identity, which defaults to UNKOWN, is pulled from the background and its value is updated, triggering the setButton method

componentWillReceiveProps(nextProps) {
    if (nextProps.role.get('identity') !== UNKOWN &&
        nextProps.role.get('identity')! = =this.props.role.get('identity'))) {
        this.setButton(); }}Copy the code

Isorphism, because the server has done the first data pull, so the above code in the client will never execute setButton method due to identity already exists, the solution can be done in componentDidMount compatibility processing

componentDidMount() {
    / /.. Determine whether it is isomorphism
    if(identity ! == UNKOWN) {this.setButton(identity); }}Copy the code

reference

React direct implementation and principles

React isomorphism gives an optimization summary