react ssr的方法有很多,但在思路上基本都是可以互通的。本文提供一种 react ssr 的方法。
触发输出的流程很简单:在nodeJs服务端接收页面的 url 请求后,分析路由,返回相对应路由的页面。
路由需要返回相对应页面,就需要在服务端解析对应的路由模块。react 提供了 StaticRouter 组件,在服务端解析路由组件。
返回的页面,希望能够直接创建出来对应的html文档片段,这正是服务端渲染的意义之一:比如我渲染一个列表页,纯前后端分离的做法是发请求,获得数据,然后渲染,能做的优化就是 loading + 占位骨架屏;而服务端渲染要直接把文档片段生成,大大减少页面抖动。此时要做的是:在服务端发送请求,在获得请求的结果之后,将数据通过 react 在服务端渲染。这是一个异步的过程,所以需要异步函数 async 等待结果。
正常情况下得到数据后,应该交由 react 渲染。但由于服务端不能生成 DOM ,所以使用 react 官方提供的 renderToString 方法,得到 string 类型的 html 文档片段,然后插入到 html 对应的 #app 中。
此处使用 axios 发送请求,发送请求的代码,完全可以是公共代码。
在 nodeJs 服务端发送一个 http 请求,这个请求也会被当作从当前页面发送的请求。但在 network 中信息展示不完整。
此时的 html 已经是要返回的结果了。
客户端渲染没有什么特别的地方,因为页面在被返回时已经是现成的页面了。
数据管理的部分也放在了客户端,因为大部分和操作数据有关的业务都在客户端;服务端只负责返回初始化数据。
项目里肯定是有路由的,当切换路由时,一切变化发生在客户端,执行的是客户端的 js 代码,所以在客户端一定也会有发送请求的代码。
客户端肯定会通过某类数据管理中心去管理请求返回的数据的,比如 redux 、mobx 等,这就需要客户端的 store 和 服务端的 store 在输出时保持一致。
所以我们还需要在服务端返回数据时,将这个数据注入到返回的 html 中,然后客户端代码要去找到这个数据。
然后我在客户端的代码的数据初始值,也要直接取这个服务端输出的数据。
此时客户端 react 也会渲染一次,这其实就是正常的 react 初始化渲染过程,而且直接拿到服务端的数据,渲染的结果不会有任何出入,所以也不会有体验上的缺陷。
// 数据要转为字符串
const contextString = JSON.stringify(ssrData);
// 注入的数据
`<script>window.initialState=${contextString}</script>`
此时可以考虑一个需求:假设我返回了某个路由的页面,当我切换页面再回来时,是否需要发送请求获取数据(可能此时服务器数据变化了的情况)?还是依然使用页面初始的数据(不请求)?对于这两种问题应该如何处理。
其实对于这个问题,考虑的出发点不应该在数据上而是在路由上,如果我切换路由时把当前页面或者目标页面的数据清空了,那么就达到实时请求的需求;否则就可以拦截请求。
页面中的路由肯定会有很多,而服务端注入到 html 中的数据最好能保持统一,此时有2种做法:
css 的话几乎完全可以按纯客户端流程去操作,当然,还有一些优化的余地。
服务端渲染图一个快,最快的那肯定是行内式,做 css in js ,但个人感觉这样做开发体验不好,维护成本高。
考虑一下纯客户端渲染,我们一般通过 mini-css-extract-plugin 包把 css 拉出去,给 class 加 hash等。它的渲染流程是,html 和 css 都匹配同一个 className,然后 js 加载完以后渲染 dom;而在 html 中创建一个 link 引入css。因此它分为 js 和 css 两布加载。
这里可以采用一部分创建内嵌式 css(做首屏优化),另一部分抽离 css 的方案。
方案:
对于第三点,事实上,一些服务端编译的过程是可以忽略的,因为服务端输出时是没有运行时的代码的
还有一点要注意的是内敛 css 不能把所有的路由都打包,这个通过路由与组件的异步加载去解决,不要考虑在 css 上解决。
区分环境:区分 dev 和 prod 环境
有此需求的地方包括:js、创建模板 html、cdn,其中 html 和 cdn 都是可以通过服务端控制的,而 js 需要通过 wepback mode:"production"配置。我通常不回去在 package.json 里写 env,而是直接区分 dev 命令 和 prod 命令,分别载入不同的 webpackjs,这样可以少在 webpack 中写太多区分环境的代码。
// package.json
"build:dev:clent": "webpack --config build/client/webpack.client.dev.js",
"build:dev:server": "webpack --config build/server/webpack.server.dev.js",
cdn:配置 externals,然后用 cdn 方式引入 react 等,这属于 webpack 优化部分。