cheungseol / cheungseol.github.io

2 stars 0 forks source link

React SSR #8

Open cheungseol opened 7 years ago

cheungseol commented 7 years ago

服务端渲染技术可以帮助提升页面的访问性能,降低加载时间。

非 SSR 场景

react 应用作为静态文件包的形式被服务端serve。访问页面时经历以下过程:

非SSR 流程

  1. 浏览器请求 URL

  2. 浏览器接收 index.html 文件作为响应

  3. 浏览器发送请求,下载remote链接和remote脚本

  4. 浏览器等待脚本下载

  5. react 渲染传进来的component 组件,最终将组件挂在到指定的 DOM 节点上

// All dependencies have to be loaded before the below
// code executes.
const renderApp = () => render(
    <App />, 
    document.querySelector("#mount")
);

在以上代码执行前,用户只能看到一个白屏的页面,在网络条件不好的情况下体验非常不好。

SSR 方案

使用非SSR 和 SSR 渲染的页面, DOMContentLoaded 几乎是相同的,但是非SSR页面在所有的脚本度加载之前,页面是空白的。SSR 渲染的页面几乎是立即可见的,脚本同时在后台一边加载。

SSR 流程

  1. 浏览器向打开的URL发送请求

  2. node server 收到请求并响应:将相关的 component 组件以一个字符串的形式渲染

// code on the server that renders the App to string. renderToString // is a function imported from 'react-dom/server'.
export const renderApp = (html, req, res) => {
  let appString = renderToString(<App />);
  let renderedApp = html.replace("<!--ssr-->", appString);
  res.status(200).send(renderedApp);
};
  1. 把渲染的 component 组件(字符串形式)注入到 index.html 文件中

  2. 把 index.html 文件发送回给浏览器

  3. 浏览器渲染 index.html 文件并下载所有其他的依赖

  4. 一旦脚本加载完毕, react 组件在 client 端会再次render。唯一的区别在于这次的渲染混合了页面上已有的视图,而不是重新覆盖(hydrates the existing view instead of overwriting it)。

混合页面上已有的视图,意味着事件 handlers 都绑定在已经渲染的 DOM 元素上,同时还保持了 DOM 元素的完整性(Hydrating a view means that it attaches any event handlers to the rendered DOM elements but keeps the rendered DOM elements intact)。

通过这种方式,既维护了 DOM 元素的完整性,同时避免了重置(reset)页面的视图。

SSR 代码实现

the best way to structure your code for server side rendered apps is to have as much of the codebase as possible to be isomorphic, i.e., capable of running on both the browser and on the server. To this end, I end up structuring my code like this

1_ggdnymfilsrw-ztvabtkmq

common 目录是整个应用的代码位置。clientserver 目录分别启动浏览器环境和服务器环境下运行的代码。

common app.js

// app.js
import React from "react";
export const App = () => <h2>Hello, World!</h2>;

client index.js

// client/index.js
import React from "react";
import { render } from "react-dom";
import { App } from "common/App";
const renderApp = () => render(<App />, document.querySelector("#mount"));
renderApp();
if (module.hot) {
  module.hot.accept("common/App", () => {
    renderApp();
  });
}

server app.js

// server/app.js
import React from "react";
import { renderToString } from "react-dom/server";
import { App } from "common/App";
export const renderApp = (html, req, res) => {
  let appString = renderToString(<App />);
  let renderedApp = html.replace("<!--ssr-->", appString);
  res.status(200).send(renderedApp);
};

注意

  1. 如果项目中使用了 css-in-js 的库,需要确保它支持 SSR

  2. 把组件需要的全部数据 push 给 root, 这样可以在服务端fetch 全部数据之前, 渲染整个应用。 当数据获取完整(available)后,再将数据传递给 root 组件,root 组件将数据向下传递给叶子节点。这样来渲染应用,然后返回给网页。

参考

Server side rendering with React and Express

React Server Side Rendering (SSR) with Express and CSS Modules

SSR with React

A simple react ssr example with preboot