react服务端渲染实践

文章目录
  1. 白屏对网站体验的影响
  2. 服务端渲染 vs 客户端渲染
  3. 为何放弃传统服务端渲染的方式
  4. 为何放弃SPA开发模式
  5. nodeJs引领的全栈开发模式
  6. react带来的同构开发模式
  7. 同构demo
  8. 总结
  9. 参考文献

前言
这篇主要总结为什么使用服务端渲染以及如何利用react的特性作服务端同构渲染,目前只是完成了一个demo网站,之后会将网站放在线上,在线上环境下,详细比较服务端渲染对于客户端渲染的提升。

白屏对网站体验的影响

在日常上网中,我们总是在追求更好的体验,其中有一项对于体验影响极大的参数,那就是网站白屏时间。通俗一点来说就是我们打开网站到网站开始出现文字图片之间的那段整个屏幕空白时间,如果这段时间很长,可想而知我们会感觉这个网站非常慢,所以这是一个必须要解决的问题。

服务端渲染 vs 客户端渲染

其实这个问题在以前的网站中并不是一个大的问题,因为以前的网站通常采用jsp或php编写页面,它们实际上就是走服务端渲染的路线,然而如今很多网站架构转变成了前后端分离式架构,渲染路线由服务端转变为了客户端渲染,于是就有了白屏过长的问题。这是为什么呢?很简单,因为渲染路线被拉长了。
客户端渲染路线:1. 请求一个html -> 2. 服务端返回一个html -> 3. 浏览器下载html里面的js/css文件 -> 4. 等待js文件下载完成 -> 5. 等待js加载并初始化完成 -> 6. js代码终于可以运行,由js代码向后端请求数据( ajax/fetch ) -> 7. 等待后端数据返回 -> 8. 客户端 从无到完整地,把数据渲染为响应页面

服务端渲染路线:2. 请求一个html -> 2. 服务端请求数据( 内网请求快 ) -> 3. 服务器初始渲染(服务端性能好,较快) -> 4. 服务端返回已经有正确内容的页面 -> 5. 客户端请求js/css文件 -> 6. 等待js文件下载完成 -> 7. 等待js加载并初始化完成 -> 8. 客户端 把剩下一部分渲染完成( 内容小,渲染快 )
说明:对同一个组件,服务端渲染“可视的”一部分( render/componentWillMount部分代码 ),为确保组件有完善的生命周期及事件处理,客户端需要再次渲染。即:服务端渲染,实际上也是需要客户端进行 再次地、但开销很小的二次渲染。

对比上面两条路线,客户端在第8步的时候才进行页面的渲染,而服务端则在4步就能将主要的页面呈现给我们,更不用说服务端渲染页面速度更快,获取初始数据更快,当然高并发下服务端渲染对服务器的压力会比较大,但是在实际情况下,可以利用集群,异步的方式来减轻服务器压力,而客户端则是我们无法控制的,所以服务端渲染是值得尝试的优化手段。

为何放弃传统服务端渲染的方式

既然jsp,php本身就是采用服务端渲染的,我们又在烦恼什么呢,直接采用jsp,php来渲染不好吗?是,如果你是个精通java,php又会js、css、html的人那么用它们来写页面可以,但是很多人并不是这么全能的人,往往写前端的人并不很懂php、java这些,那他们开发jsp、php相当于需要新学一门语言,更何况调试这些页面还得后端服务器的支持,极大的限制前端人员的开发。

为何放弃SPA开发模式

为了明确前后端的职责分工,我们提出前后端的分离的SPA(单页面应用)型开发模式。其主要思想为:将从数据处理和页面渲染交由客户端的js来进行,而动态数据则通过ajax请求服务端接口获取,形成了下图所示的结构:
Alt text
这种模式下,前后端的分工非常清晰,看起来是如此美妙,然而性能问题却非常突出,其中最严重的莫过于上面提及的极长的页面白屏时间,而且等于完全放弃了同步场景。

nodeJs引领的全栈开发模式

SPA式的前后端,是从物理的层面去区分前端和后端,认为客户端就是前端负责,服务端就是后端负责,而随着nodejs推广,js开始进入到了服务器端开发,前端可以也从事服务器开发了,于是我们就有了从职责上去划分前后端的想法并赋予前端更大的掌握领域,具体来说就是:

  • 前端:负责View和Controller层。
  • 后端:只负责Model层,业务处理/数据等。
    当前端掌握了Controller层,就可以自己实现服务端渲染,根据场景来选择做服务端渲染还是客户端渲染,可以自行设计渲染路由,且只需同后端协商好接口设计之后,便能够利用mock数据进行调试,不在受后端服务器的限制,可以说同时获得前两种模式的优点,当然对前端自己的要求也变高了,需要了解服务器端的相关知识。

    react带来的同构开发模式

    当我们实现服务渲染时,我们需要一套模板来将动态数据渲染为html,但不是所有的地方都需要服务端进行渲染,对于简单页面,或者动态切换的部分页面,无需从服务端进行渲染,我们自然在客户端也会保有一套渲染模板,那么我可不可以只写一套模板,然后服务端和客户端都可以使用这套模板来做。这是可行的,这种开发模式叫做同构开发。然而同构是种很难实现的开发模式,它面临如何统一两个不同的开发环境,如何统一状态,如何做到不重复渲染,目前能做到同构渲染的框架并不多,我了解的只有ng2、react、vue2可以解决,其中react与vue2 是通过由react研发的虚拟dom技术实现的,ng2则是通过在服务端引入独立渲染引擎直接渲染出dom结构来实现。目前,我只使用过react来实现同构渲染,所以无法评判三者实现效果的优劣,不过react无疑是最为主流的做同构开发的前端框架。
  • 简单介绍下react虚拟dom:其实也不难理解,就是用js对象模拟原生dom树并保存在内存中,然后根据js对象树生成一颗真正的dom树并append到html中。在发生变化重新渲染一个js对象树,然后与原来的对象树进行比较,记录两颗树的差异最后把它应用到真正的dom树上面。它会在进行差异比较时找寻一种尽量减少对真正dom的操作次数的更改方法,因为操作dom是耗时极高的一件事,这样就可以提升渲染的效率,同时还因为有了这颗虚拟的jsdom树,我们在服务器完全可以用同样的思路把它渲染出来只不过渲染成的是htmlString。

实际上react也提供了react-server-render的一套解决法案,其实核心方法就是renderToSting和reanderToStringMarkUp,其中后者渲染结果中不包含react-data-checksum等react相关属性,即渲染出的是纯html结构,为什么会有两种渲染方式,在这里是为了解决一个之前提到的重复渲染的问题,既然客户端使用了js渲染那即使不依赖服务端同样可以渲染出html结构,而我们肯定不想让它再次渲染我们在服务器已经渲染好的部分,所以当我们使用renderToSting进行渲染的时候,会生成相应的checksum(校验和) ,这样客户端在渲染时就会复用服务端已生成的初始dom并增量更新,所以renderToString就是为了渲染同时被两端使用的组件而reanderToStringMarkUp则可以更快的渲染那些只在服务端渲染的组件。事实上react不仅仅自己支持服务端渲染,它的主流插件也同样支持,包括react开发者熟知的redux、react-router。正因为这样,我们才能真正实现同构渲染,因为实际开发中我们大多离不开这些插件。

同构demo

我自己实现了react+redux+react-router+cssmoules+server-render+async+koa的demo,老实说这个体量已经不能叫做demo了,看起来会有点吃力,以后我会完善文档说明。目前基本解决了用react做中大型项目同构时遇到了各种困难。
项目的github地址:https://github.com/lsa2127291/ownsite-h5
目前还有不少场景没有实验,比如非常重要的用户登录场景,不过项目仍在持续更新中,最终会开发为一个h5博客平台。

总结

对于同构,我参考了网站上非常多的demo、文章,然而越深入的了解和实现同构,越让我觉得同构的难度确实非常大,很多实际场景是很复杂的,而很多文章仅仅只是在讲概念,将基本实现,这绝对是满足不了实际开发,当然如果要面面俱到那可以用一本书的长度来写如何同构了,我也只能用一个相对完整的demo来演示我设计的同构,通过查阅资料,很容易发现,同构的方式网上一篇文章是一个样子,根本提不出一个标准的实现方式,不过思路基本算是一致,利用react提供的服务端方法与客户端共用一套组件,并做router同步和状态同步,我的实现只是相对完整,但是只是众多方式中的一种,参考之后不用拘泥于我实现的方式,根据需求和自己的理解来设计同构即可,有新的思路和疑问也可以联系我一起探讨,共同学习,一起进步。

参考文献

前后端分离项目实践分析
前后端分离的思考与实践(一)
怎么更好的理解虚拟DOM?
React 同构实践与思考
我为什么选择 Angular 2?
Redux 中文文档
React Router 中文文档
react-production-starter