React Ssr

React SSR 总结

现在开发 React 基本都用 create-react-app

的确, opinionatedconfigurable 要方便,就像 prettierlinter 方便, create-react-appwebpack 方便。

当然,他们都不是一个层面的东西,不能这么比。

那么,怎么做 SSR 呢?

项目结构方面

// client.js
import React from "react";
import ReactDOM from "react-dom";

import App from "./App";
import { AProvider, BProvider } from "./ManyProviders";
// 1. 把 App等Component 导出出来,避免自己服务端代码还需要用各种 babel。。。。
export { App, AProvider, BProvider };

// 2. 把所有的在 import 阶段就会调用到 浏览器特有API 的代码用 isBrowser 包裹起来
const isBrowser =
  typeof window !== "undefined" && typeof window.document !== "undefined";
if (isBrowser) {
  const { default: registerServiceWorker } = require("./registerServiceWorker");
  ReactDOM.render(
    <AProvider>
      <BProvider>
        <App />
      </BProvider>
    </AProvider>,
    document.getElementById("root")
  );
  registerServiceWorker();
}

// server.js
// 1.1 服务端不需要再考虑 babel等东西 了
const { AProvider, BProvider, App } = require("./build/static/js/main.xxxx.js");
// 3. 使用 Provider 的形式,让 bundle.js 内始终没有状态,状态都是从外面传进去的。
//    因为 server 是并发的,而 commonjs 的 module 没有并发
const app = (
  <AProvider initialXXX={a_ctx}>
    <BProvider initialXXX={b_ctx}>
      <App />
    </BProvider>
  </AProvider>
);

server.js 里写的当然不是 jsx, 而是 React.createElement, 这里只是偷个懒,下同。

renderToNodeStreamwindow.__INITIAL_STATE__

renderToNodeStream 得到的是一个 stream, window.__INITIAL_STATE__ = "JSON.string(xxxx)" 是一个 string, 他们都要被发送给浏览器。
而且,很多情况下, window.__INITIAL_STATE__ 是要在 renderToNodeStream 结束后才能得到。

// server.js
const client = new ApolloClient(xxx);
const app = (
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>
);
await getDataFromTree(app);
// 接下来 renderToString 当然是很简单的,然后可以各种拼接字符串
// 但如果是 stream 呢? 下面仅仅是伪代码,只是为了提醒一件事情: stream 的拼接中,`window.__INITIAL_STATE__`的生成应该在 node_stream 结束之后
const node_stream = renderToNodeStream(app);
node_stream.pipe(res);
node_stream.on('end', _ => {
  const __APOLLO_STATE__ = apollo_client.extract(true);
  // const __APOLLO_STATE__JSON = JSON.stringify(__APOLLO_STATE__).replace(/</g, "\\u003c");  // 这里不是重点
  res.write(`<script>window.__INITIAL_STATE__=${__APOLLO_STATE__JSON}</script>`);
  res.write(`<script src="main.xxxxxx.js" />`)
});

stream 的拼接中, window.__INITIAL_STATE__ 的生成应该在 node_stream 结束之后。
bundle.js/main.js 应该在 window.__INITIAL_STATE__ 之后。。。或者 bundle.js 里把 hydrate/render 过程放在 document.onload 中。

react-routerStaticRouterrenderToNodeStream

const router_ctx = {};
const app = (
  <StaticRouter context={router_ctx}>
    <App />
  </StaticRouter>
);

// 我们可以全部渲染完之后,才知道是否应该 redirect
const html_str = renderToString(app);
if (router_ctx.url) {
  return res.redirect(router_ctx.url);
}

// 但是如果用了 renderToNodeStream 之后要怎么办呢?
// http 可不支持传输 body 传到一半,突然来个 Redirect: 302 状态码
// https://github.com/ReactTraining/react-router/issues/6191

CSS in React

使用 @xialvjun/create-react-style.
直接在 node_stream 上渲染出 style 标签,避免后续的 stream 操作.
另外使用 hash 函数生成稳定的 className, 或者抽取出 Providerstylis_cache.

[top]

comments powered byDisqus