代码请参考:learn-react-ssr
什么是SSR?
SSR 是服务端渲染。它是后端模版渲染和前端(SPA)单页应用的结合。
它有 2 种常见的模式:
- 完全使用后端路由渲染模版
- 后端只渲染首次访问的内容,后面交给单页应用托管
这两者的主要区别是:后端路由的使用程度不同。在NodeJS的SSR框架中(比如NextJS),使用的都是后一种。
SSR 后端渲染的原理
后端渲染的逻辑主要参考 server/router/index.js 文件中针对 get(*)
所有 GET 请求的处理。
处理逻辑如下图所示:
这里面有2个部分需要特别注意。一个是 getInitialProps
,另一个是将React组件对象渲染成字符串并输出到模版文件中。
getInitialProps 方法
getInitialProps 方法是react组件类上的静态方法。以List组件为例,在方法内发起了异步请求:
getInitialProps 方法返回的数据,会在组件渲染前,传给组件。因此,在后端渲染时,要执行当前组件的 getInitialProps 方法,并且将结果通过 ssrData
参数传给组件。
这里的 getInitialProps 是在后端渲染时执行的,所以使用的是 NodeJS 环境,可以使用 fs、http 等库。
渲染 React 组件对象
从上图看到,这里用到了 React.createElement()
方法来获取组件渲染后的html代码。然后将模版文件的占位符,替换成组件html代码。
为了让 react 单页应用能在前端正常运行,用webpack对前端目录 /client
进行打包,并且将模版文件中对应的占位符替换为打包后的JS文件地址。
有了这些,访问页面后,通过浏览器查看当前页面元素,就能看到被替换后的模版的 html 全部内容。而不是像 SPA 那样,只有 JS 引用信息,没有当前页面的 HTML 信息。
注意:这里把 ssrData 以及 ssrPath 放到了前端运行时的全局对象 window 上,这个是为了在前端无缝切换成SPA。下面会提到。
前端加载原理
前端加载分为2个部分:
- 首屏渲染。也就是加载前面提到的
React.createElement()
渲染结果,本质上就是一个纯 html 文件。 - 切换为单页应用。从纯 html 文件切换成 SPA,此时页面状态(state)和路由(router)都由前端应用接管。
首屏渲染
流程图如下:
本质上就是后端将渲染后的html文件,返回给前端。流程图中提到的替换模版内容,在上一部分已经说过了原理。
切换 SPA
流程图如下:
当加载完首屏的HTML文件,就会加载HTML文件中的JS文件。这些JS文件就是SPA应用打包的结果。加载JS后,整个页面就会变成SPA应用。
为了不丢失首屏渲染的状态和路由信息,ssrData
和 ssrPath
在「后端渲染React组件对象」时,挂在 window 对象上的。并且在前端SPA应用中,直接将 window 上的 ssrData
和 ssrPath
传给了深层组件。如下图所示:
在Router类的实现中,会检查当前路径是否是 ssrPath
(如下图所示):
- 如果相等,那么当前页面是首次渲染的页面,那么它的
getInitialProps
方法是在NodeJS环境下运行的无法在前端运行,需要将运行结果ssrData
传下去 - 如果不想等,那么当前页面一定不会是首次渲染的页面,不需要传
ssrData
ssrData
传递下去后,组件类会将 ssrData
和组件状态进行合并。组件定义在 client/router/pages 中,通过查看继承关系,可以看到这些组件均继承自 Base 类。
在页面切换路由时,Base 类的构造函数,以及componentWillMount() 函数均会执行。
在 Base 类的构造函数中,会检查是否有 ssrData
从 Router 传递过来。如果有,那么就是首次渲染的页面,需要将 ssrData
赋值给 state。
当用户进入页面时,会触发 Base 类的 componentWillMount()
函数。如果有 ssrData
,那么当前页面是首次渲染的页面,getInitialProps
方法在NodeJS环境下运行,已经在构造函数中,读取并且赋值给了state;如果没有 ssrData
,那么就需要在浏览器环境下执行 getInitialProps
方法,并且将结果赋值给 state。
从而实现了不同情况下(SPA路由跳转、首屏渲染到SPA应用)的状态处理。
这里的 getInitialProps 是运行在浏览器环境。
特别说明:getInitialProps
老版的Next.js中,getInitialProps
既可以在nodejs环境,也可以在浏览器环境运行,内部会自动处理(类似本文的实现)。
新版的Next.js中,使用 getStaticProps
和 getServerSideProps
替换了 getInitialProps
。语义更好,直接指明了运行环境。文档如下: