原文连接https://www.tuicool.com/articles/z2eQNrI前端
公司内部的公用图标字体平台 iconfont 上线一年以来,运行良好,在内部承担起了客户端减容、提升前端开发效率的任务。但因为其臃肿的架构致使的运行效率底下和页面卡顿等问题也一直给使用者形成诸多不便。咱们对项目老架构致使的许多问题进行了探索总结,并针对问题提出了 react 同构的解决方案。node
在进行旧版平台开发的时候,咱们采用了当时淘宝推出的先进思想: 中途岛 midway 先后端分离方式 ,搭建了一个 node 中间层进行页面的渲染,以求提高页面的渲染速度。咱们旧版平台的结构以下:react
从图中咱们能够看到,尽管咱们前端掌握了 server,能够进行页面渲染的控制,可是服务端的渲染和前端的渲染和路由依然是割裂的,之间有不少冗余的内容。致使这些冗余的主要缘由,其实仍是先后端渲染方式不一致以及先后端代码的分离。webpack
咱们知道,在传统的 MVC 架构的项目之中,js 代码只占 View 层的很小的一部分。随着项目的渐进发展,前端功能的复杂度日益增高,致使项目难以维护;同时先后端语言并不一致(咱们都知道 Java 跟 JavaScript 基本上是雷锋和雷峰塔的关系),不一样的开发在一个项目里操做极为不便,所以才产生了先后端分离。git
可是随着 js 向服务端的进发,咱们的中间层 server 也采用支持 js 的 node 来进行架构,因此先后端语言不一致的问题基本上抹平了;而前端功能复杂这一点,从刚才的分析咱们也能够看到,其实前端和后端在路由、渲染这些功能上是有很大的重合,所以前端的 server 和前端逻辑项目没有必要进行分离。github
实际上,这里咱们的先后端分离,已经有传统意义上前端和后端代码的分离、服务端和浏览器客户端的分离,演变为后端数据提供和前端提供渲染的分离。web
若是将先后端代码糅合在一块儿,那么渲染这里将会是服务端逻辑和客户端逻辑的一个结合点,它们的模板、渲染方式都必定要一致,才能减小开发的工做量。算法
对于咱们旧版项目来讲,服务端采用 handlebars 做为模板,而前端采用 MVVM 模式的 avalon 的模板,二者在用法和理念上都是有必定冲突的。其中 MVVM 模式在服务端渲染中最棘手的问题就是: 要实现双向数据绑定,必需要经历一次 DOM 渲染 。这样就致使后端只能渲染一个中间状态的模板,而后还须要前端在更改一次 DOM,没法达到『直出』的效果。npm
这个问题看似困难,但在 react 出现以后,却获得了完美的解决:react 基于 virtual DOM,不须要扫描 DOM 来创建双向绑定关系,只须要在每次状态变更时进行 diff,有变化才会进行更新。所以,咱们能够在服务端直接渲染出 DOM 结构,若是前端最终生成的虚拟 DOM 跟后端直出的 DOM 保持一致,那么就不须要更改 DOM 结构,大幅度提高渲染速度。redux
若是要实现先后端代码同构,其实只要保证两个一致便可: 包管理工具 和 模块依赖方式 的一致。这里咱们能够看到,这两者的一致性都能得以保证:
有了这两者的保证,咱们就能够完美的解决同构的问题,剩下须要考虑的就是如何处理服务端渲染了。
react 自诞生之初就对服务端渲染很是重视,它的『全家桶』都对服务端渲染进行了良好的支持。所谓的『全家桶』指的就是你们耳熟能详的 react 御三家:react、 react-router 和 redux 。
ReactDOM 在这里提供的支持就是 ReactDOM.render
和 ReactDOM.renderToString
函数,其中前者会在浏览器端生成 DOM 结构,后者会在服务端生成对应的 HTML 字符串模板。React 会在生成的 DOM 结构上添加一个 data-react-checksum
的属性,这是一个 adler32 算法的校验和,用以确保两份模板的一致性。
同时 react 的生命周期在先后端渲染过程当中也有所不一样。前端渲染的组件拥有完整的生命周期,然后端渲染仅有 componentWillMount
的生命周期。这就意味着,若是咱们想进行先后端共同操做的逻辑,如发送数据请求等,能够放在 componentWillMount
的生命周期中;若是想单独处理客户端的逻辑,能够放在其余生命周期,如 componentDidMount
中。
react-router 是 react 的路由 - 视图控制库,能够书写便捷的声明式路由以控制不一样页面的渲染。react-router 自己是一个状态机,根据配置好的路由规则,和输入的 url 路径,经过 match
方法找到对应的组件并进行渲染。
这套机制在前端和后端都是相通的,例如在后端,就是下面这样一种实现形式来进行渲染:
app.use(async (ctx, next) => {
match({
location: ctx.originalUrl,
routes
}, callback)
// 渲染完成以后,调用 callback 回调
// 将 <RouterContext> 组件 renderToString 返回前端便可
})
对于前端来讲,其实也是处理的上面这些逻辑,不过它被很好的封装在 <Router>
组件中,咱们只须要写好声明式的路由,这一切就能够随着 url 的变化自动发生。
redux 是 react 的数据流管理库,它对服务端渲染的支持很简单,就是 单一 store 和 状态可初始化 。后端在进行渲染的时候会构建好单一的 store,并将构建好的初始状态经过以 JSON 格式,经过全局变量写到生成好的 HTML 字符串模板上。前端经过获取初始状态,生成跟后端渲染完成后如出一辙的 store,就能够保证先后端渲染数据的一致,以确保先后端生成 DOM 结构的一致。
项目在使用了 react 进行同构构建以后,首屏渲染性能获得了明显的提高,以前页面大约 1500ms 才能展现关键数据,而以后的页面只须要 230ms 就能够进行数据展现了。
同时项目获得了精简,由以前的两个项目合并为一个,代码也得以通用。
项目通过 react 同构,以前的许多问题都得以解决:
其实新的 react 服务端渲染架构并非对以前『中途岛』的推翻,而是它理念的演进,核心思想都是由服务端来控制渲染,只是这里将以前互不干涉的先后端项目糅合到了一块儿,使用同构的方式简化了渲染层的工做。对于已使用 node 中间层的项目,不妨尝试一下 react 同构的技术方案,它会使你的开发效率和首屏性能获得飞速提高。