背景

为什么是React?

React今年在国内特别火,一时间虚拟DOM(Virtual DOM)等酷炫概念一下刷新了很多前端开发同学的三观,关于性能优劣的争论也在知乎上看到不少。不得不说React解决了一些前端项目开发的痛点,而我最近的一年多的工作重心,都在兴趣部落这样一个基于兴趣社交的web产品上,有很多感同身受的地方。兴趣部落这个产品从初期只有移动端的2、3个页面,发展到现在50+移动页面,加上PC版的最近上线,中间经历了从2-3人的小项目到10+人团队的大型前端项目的巨大转变。这个过程中除了人员相对业务的线性增加,代码量、维护成本也是以指数速度增长的,很快代码臃肿、难以维护与测试等问题就凸显出来。虽然内部经过一些轻量的重构优化,但开发模式还是与高度的迭代节奏很不匹配。这时候,React+Webpack的组件开发模式让我眼前一亮,暗下决心要让这样的先进开发模式推广到项目团队,好东西一定要让大家有所受益,而不仅仅是技术的尝鲜、摆设。

为什么要在服务端渲染?

除了代码维护性的问题,项目代码膨胀导致的一个问题是基础库、公共资源的变大,从而导致页面加载性能日益下降。在使用React时,也第一时间考虑到对加载性能的影响,本来页面就要等待ajax返回数据,页面会不会因为React的引入变得更慢呢?不要说React那点大小根本不是事儿,在移动网络下(包含2G啊T_T)任何资源都是很珍贵的。有没有同时提高代码维护性,又能提升页面加载速度的好事呢?答案终于回到主题,就是在服务端渲染React。这里可以一并总结该方案的好处:

  1. 利于SEO
  2. 加速首屏渲染速度
  3. 享受React组件式开发的优势:高复用、低耦合
  4. 前后端维护一套代码(代码同构)

原理

如果听到AngularJS在服务器端使用,你可能会很惊讶。但是对ReactJS,完全不必如此,因为React很好的分离了DOM的操作,使得在服务器端输出页面字符串有了可能。最新的React版本0.14.1中,已经彻底将服务端需要的源码分离出来。react-dom/server

我们使用的核心API就是ReactDOMServer.renderToString,它只会在服务端使用,并返回组件渲染数据后的HTML字符串。而接下来要做的,就是将这个HTML片段拼接回页面模版,返回到前端浏览器进行用户侧的显示。

这里还要补充一句,React组件在服务端的生命周期方法,只会执行到componentDidMount之前的方法,因为在服务端没有组件挂到文档DOM树的概念。

核心步骤

Node端的组件加载

我们要在Node端渲染组件,首先需要加载到组件,像这样:

但是Node默认状况是不懂怎么解析JSX文件的,所以要在之前加上:

React组件渲染

加载到了React组件,等到后端的数据data也拿到后,就可以进行组件的渲染了:

data是一个json对象,比如data={'type':'test'},这时候data会以props的形式传递给组件,类似于在jsx中这样写:

拼接返回HTML

这时候组件的HTML已经渲染完毕了,可以作为HTTP的返回体的一部分返回到浏览器了,这时候大家可以根据自己的项目框架进行操作。我们在这里使用了Koa的框架,并使用了ejs的模版,所以可以参考代码:

到这里基本就完成了在服务端进行React组件的渲染,但其中还有一些小问题,我们接着继续探讨。

延伸话题

关于前后端共用代码的问题

我们的JSX文件在前端与Node端是完全复用的,而服务端用到的实际代码比浏览器需要的更少,但还是有一些差异需要注意:

前后端环境的判断方法

我个人建议通过window对象来判断,因为之前使用过moduel对象来判断,会因为webpack的打包代码导致客户端侧的运行会有问题。

资源加载差异化

我们在进行组件化开发的模式式,样式文件也是通过require方法来引入的,而Node同样是无法默认解析样式文件的。类似的,一些浏览器环境需要的工具脚本,一定是依赖浏览器API的(BOM对象),所以也不应该在Node加载,于是就有了:

生命周期方法注意

前面也提到了,React组件在服务端只有componentDidMount之前的方法会被执行,因此也要保证在getInitialState、render等方法中不会使用到浏览器 API,而将相应的操作放到componentDidMount中执行,如ajax。另外在使用mixin时也需要注意。

关于前后端复用代码这事儿

有必要吗?

有同学会想,既然都支持服务端渲染了,全部走后端渲染呗,还搞啥前端部分的代码。(干脆我用php也能搞定,那可是世界上最好的语言,不就是动态内容嘛)
我是这么想的,原始的纯前端渲染还是有必要的,一是开发调试方便,二是在任何情况下都有另外的选择,比如目前我们采用的方案是集群的负载均衡,一个用户访问的请求,有可能是通过html+js的页面渲染,也有可能是服务器直接吐出渲染好的页面,只是其中的权重有所不同(服务端渲染的概率会更高)。当前端或后端逻辑出现异常或服务波动,对于用户来说都不受影响。

如何复用模版?

对于页面的容器来说,纯前端渲染的容器是HTML文件,而后端则是ejs的模版文件。发布时需要对二者同时发布,并需保持内容的一致性。这里可以通过构建工具来解决,服务端需要的ejs相对html文件只是多需要一些占位变量。

后端性能相关

请求放在后端渲染,势必会多一些压力点。除了需要在接入层做到负载均衡,在缓存上也可以考虑分布式缓存等优化策略。在数据超时的情况,也需要保证页面的正常输出,而此时返回的内容其实就等同于非服务端渲染的HTML页面内容。

写在最后

在React后端渲染的范畴内,目前在国内没有找到较为系统的实践总结,同时在React研发团队的支持方面,可以看到目前还有很多优化的工作正在计划(国外有不少开发者吐槽过React在服务端的性能)。但我希望可以有更多的人来尝试这个方向,并与我们进行交流,共同进步~
欢迎感兴趣的同学进行留言,或关注我的微博 http://weibo.com/lovelovelt

原创文章转载请注明:

转载自AlloyTeam:http://www.alloyteam.com/2015/10/8783/

  1. 这两天也在折腾服务端渲染,确实对client的代码还是 有影响的,比如在 component 里面,有 var imgSrc = require(‘./assets/logo.png’) ,然后 render 里 ,

    render(){
    return (

    );
    }

    因为我们在node里,关闭了图片、LESS、CSS的require,返回的null,上面代码,会导致输出到浏览器后,React diff 发现和server渲染的不一致,会有个warning。

    不知道你们有没有遇到过呢

  2. 现在客户端配置越来越强大,不充分利用客户端,反而把大部分的渲染放在服务端,不一定是明智的决定。

    • 代码复杂性并没有增加多少,PC上其实没有太大必要,除非有较强的SEO需求。
      移动端上的首屏直出还是很有价值的,这也是选择服务端渲染的初衷[呵呵]

  3. 仅用来做了模板渲染?想问下,事件怎么办?还是没理解用react渲染比起普通的模板引擎渲染的优势在哪里?

    • 服务端渲染后,前端还是需要加载react的页面逻辑(所谓的前后端代码复用),前端还是会进行一次react的render。但此时前端的render已经可以和直出的HTML进行diff,按需做UI的局部刷新,事件就像纯前端的react使用一样,在jsx中进行绑定。
      在后端,react渲染比起普通的模板渲染,优势在于前后端的组件开发是可复用的。我们目前的开发模式是,现在前端进行开发,开发完成后,简单地做一些服务端的兼容(比如资源加载差异化,上面有讲),5分钟就能把前端的逻辑,跑在服务端了。
      react在我眼中的价值是,组件可复用性和代码可维护性。