« 回到主页

React 服务端渲染

参考网址

http://www.qingpingshan.com/jb/javascript/187275.html

1 传统SPA的渲染

一个React架构的SPA(单页应用)的渲染:客户端请求资源 –> 返回index.html模板 –> 请求JS文件并加载 –> React执行,挂载组件 –> 进入路由 –> 根据组件生命周期而做的初始化操作 –> 渲染组件 在这种模式下,一开始客户端只会收到一个没有内容的HTML文件,等待JS文件加载完毕后才会执行后续操作并渲染出页面。也就是说,无论是哪个路由下的页面,一开始都会先进入index.html这个入口,然后在客户端进行接下来的渲染,这对于大部分用户确实没有什么问题,但对于部分场景和搜索引擎就不是这样了。

2 服务端渲染的好处

3 同构方案

3.1 什么是同构?

同一套代码,既可以运行在客户端(浏览器),又可以运行在服务器端(node)。

3.2 为什么要做同构?

在同构模式下,客户端的代码也可以运行在服务器上,在服务器端就可以将数据组装成页面返回给客户端。

3.3 React如何实现组件同构?

基于React的同构开发要归功于React提供的服务端渲染。 由于React本身的设计特点,它是以Virtual DOM的形式保存在内存中,这是服务端渲染的前提。React可以从Virtual DOM中生成一个字符串,而不是真正的DOM,这使得可以在客户端和服务端使用同一个React Component。

4 服务端组件的生命周期

服务端渲染只会走一遍生命周期,并且在第一次render后便会停止。一旦渲染为字符串,组件就会只调用位于render方法之前的组件生命周期方法,只有constructor、componentWillMount和render方法会被各调用一次,也就是说,componentDidMount和componentWillUnmount方法不会在服务端渲染过程中被调用,而componentWillMount在客户端渲染和服务端渲染下均有效。 当新建一个组件时,需要考虑到它可能既在服务端又在客户端进行渲染。这一点在创建事件监听器时尤为重要,因为并不存在一个生命周期方法会通知React Component是否已走完了整个生命周期。在componentWillMount内创建的所有事件监听器及定时器都可能潜在地导致服务端内存泄漏,最佳做法是只在componentDidMount内创建事件监听器及定时器,然后在componentWilUnmount内清除事件监听器及定时器。

5 状态管理

用Redux来管理React组件的状态,并配合强大的中间件Devtools、Thunk、Promise等来扩充应用。当进行服务端渲染时,创建store实例后,还必须把初始状态回传给客户端,客户端拿到初始状态后把它作为预加载状态来创建store实例。为什么客户端要拿到state来渲染?要保证客户端和服务端渲染的组件一致,否则客户端上生成的markup与服务端生成的markup不匹配,客户端将不得不再次加载数据,造成没必要的性能消耗。

6 路由方案

客户端路由使得客户端可以不依赖服务端,根据hash方式或调用history API,不同的URL渲染不同的视图,实现无缝的页面切换,用户体验极佳。但服务端渲染不同的地方在于,在渲染之前,必须根据URL正确找到相匹配的组件返回给客户端。 路由问题可以交由React-router解决。React Router为服务端渲染提供了两个API:

7 静态资源处理方案

在客户端中,使用了大量的ES6/7语法,jsx语法,css资源,图片资源,最终通过webpack配合各种loader打包成一个文件最后运行在浏览器环境中。但在服务端,不支持import、jsx这种语法,并且无法识别对css、image资源后缀的模块引用,那么要处理这些静态资源,需要借助相关的工具、插件来使得Node.js解析器能够加载并执行这类代码。 开发环境和产品环境配置两套不同的解决方案:

7.1 开发环境

7.2 产品环境

使用webpack分别对客户端和服务端代码进行打包。对于服务端代码,需要指定运行环境为node,并且提供polyfill,设置__filename和__dirname为true,由于是采用CSS Modules,服务端只需获取className,而无需加载样式代码,所以要使用css-loader/locals替代css-loader加载样式文件。

8 动态加载方案

对于大型Web应用程序来说,将所有代码打包成一个文件不是一种优雅的做法,特别是对于单页面应用,用户有时候并不想得到其余路由模块的内容,加载全部模块内容,不仅增加用户等待时间,而且会增加服务器负荷。webpack提供一个功能可以拆分模块,每一个模块称为chunk,这个功能叫Code Splitting。可以在代码库中定义分割点,调用require.ensure,实现按需加载,而对于服务端渲染,require.ensure是不存在的,因此需要判断运行环境,提供钩子函数。

« 回到主页