You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
hydrate() Same as render(), but is used to hydrate a container whose HTML contents were rendered by ReactDOMServer. React will attempt to attach event listeners to the existing markup.
React expects that the rendered content is identical between the server and the client. It can patch up differences in text content, but you should treat mismatches as bugs and fix them.
前两年服务端渲染和同构的概念火遍了整个前端界,几乎所有关于前端的分享会议都有提到。在这年头,无论你选择什么技术栈,不会做个服务端渲染可能真的快混不下去了!最近刚好实现了个基于React&Redux的同构直出应用,赶紧写个文章总结总结压压惊。
前言
在了解实践过程之前,让我们先明白几个概念(非新手可直接跳过)。
什么是服务端渲染(Server-Side Rendering)
服务端渲染,又可以叫做后端渲染或直出。
早些年前,大部分网站都使用传统的MVC架构进行后端渲染,就是实现一个Controller,处理请求时在服务端拉取到数据Model,使用模版引擎结合View渲染出页面,比如Java + Velocity、PHP等。但随着前端脚本JS的发展,拥有更强大的交互能力后,前后端分离的概念被提出,也就是拉取数据和渲染的操作由前端来完成。
关于前端渲染还是后端渲染之争,可以看文章后面的参考链接,这里不做讨论。这里照搬后端渲染的优势:
什么是同构应用(Isomorphic)
同构,在本文特指服务端和客户端的同构,意思是服务端和客户端都可以运行的同一套代码程序。
SSR同构也是在Node这门服务端语言兴起后,使得JS可以同时运行在服务端和浏览器,使得同构的价值大大提升:
基于React&Redux的考虑
其实Vue和React都提供了SSR相关的能力,在决定在做之前我们考虑了一下使用哪种技术栈,之所以决定使用React是因为对于团队来说,统一技术栈在可维护性上显得比较重要:
React提供了一套将Virtual DOM输出为HTML文本的API;
Redux提供了一套将reducers同构复用的解决方案;
方案与实践
首先先用脚手架生成了基于React&Redux的异步工程目录:
可以看到,现有的异步工程,构建会使用web-webpack-plugin将所有
src/pages/xxx/index.js
当做入口为每个页面编译出异步html、js和css文件。1. 添加Node Server
既然要做直出,首先需要一个Web Server吧,可以使用
Koa
,这里我们采用了团队自研基于Koa
的IMServer
(作者是开源工具whistle的作者,用过whistle
的我表示已经离不开它了),Server工程目录如下:由于是一个多页面应用(非SPA),上文提到之前团队的实践中Controller逻辑并不是通用的,也就是说只要业务需求新增一个页面那么就得手写多一个Controller,而且这些Controllers都存在共性逻辑,每个请求过来都要经历:
那我们为什么不实现一个通用的Controller将这些逻辑都同构了呢:
上述代码相当于将处理过程钩子化了,只要同构代码提供相应的钩子即可。
当然,还得根据页面生成相应的路由:
至此服务端代码已基本完成。
2. 同构构建打通
上一步服务端代码依赖了几份同构代码。
web-webpack-plugin
生成的页面xxx.html再编译的模版函数template我选择了通过构建编译出这些文件,而不是在服务端引入
babel-register
来直接引入前端代码,是因为我想保留更高的自由度,即构建可以做更多babel-register
做不了的事情。上述代码将Controller需要的同构模块和文件打包到了server/目录下:
3. 实现同构钩子
还需要在同构模块中实现通用Controller约定。
至此同构已基本打通。
4. 异步入口&容灾
剩下来就好办了,在异步JS入口中使用
ReactDOM.hydrate
:容灾是指当服务端因为某些原因挂掉的时候,由于我们还有构建生成xxx.html异步页面,可以在nginx层上做一个容灾方案,当上层Svr出现错误时,降级异步页面。
踩坑
像因为生命周期的不同要在
componentDidMount
绑定事件,不能在服务端能执行到的地方访问DOM API这些大家都应该很清楚了,其实大概只需要实现最主要几个同构的基础模块即可:当然我要说的还有一些依赖客户端能力的模块,比如wx的sdk,qq的sdk等等。
这里稍微要提一下的是,我最初设计的时候想尽可能不破坏团队现有的编码习惯,像location、cookie之类的这些模块方法在每次请求过来的时候,拿到的值应该是不一样的,如何实现这一点是参考TSW的做法:https://tswjs.org/doc/api/global,Node的domain模块使得这类设计成为可能。
但是依旧要避免模块局部变量的写法(有关这部分内容,我另写了一篇文章可做参考)
ignore-loader
忽略掉依赖的css文件core-js
包导致内存泄漏这部分core-js的上的issue也有说明为什么要这么做:
babel/babel-loader#152
其实在node上es6的特性是都支持了的,打包出的同构模块需要尽可能的精简。
后续思考
这整个设计其实把构建能力抽象出来,钩子可配置化后,就可以成为一个直出框架了。当然也可以像Nextjs那样实现一些Document等组件来使用。
当前设计由于Server的代码依赖了构建出来的同构模块,在日常开发中,前端做一些页面修改是经常发生的事,比如修改一些事件监听,而这时候因为js, css资源MD5值的变化,导致template.html变化,故而导致server包需要发布,如果业务有有多节点,都要一一无损重启。肯定是有办法做到发布代码而不用重启Node服务的。
以上就是本文的所有内容,请多多指教,欢迎交流(文中代码基本都是经过删减的)~
参考资料:
The text was updated successfully, but these errors were encountered: