此文已由作者张硕授权网易云社区发布。
欢迎访问网易云社区,了解更多网易技术产品运营经验。
网易美学主站在最初开发时,因为各种历史原因,引入了例如JQuery,Bootstrop,Angular, React等框架,代码结构比较混乱,给后续的开发和维护带来了很大的不便。所以对它进行了重构。下面,我会从以下三个方面对主站的重构方案进行介绍:
我们为什么进行重构?
如何使用React进行同构
同构过程中遇到的问题以及解决方案
我们为什么要进行重构?
Before
对于同一个组件,需要分别使用模板和React组件实现两次
早期的主站使用Express作为Node层路由的同时,使用了类似于Jinja的Nunjucks作为javascript 模板引擎,进行HTML文件的渲染,也就是说,我们的网站是一个多页应用,Nunjucks渲染满足了SEO的需求。之后出于封装和组件的管理引入了Reactjs,对于一个页面的开发,往往需要两步:
使用Nujucks书写template以及对应的css样式;
页面加载后,对某些需要组件化的DOM 节点进行React组件的替换
对于每个页面,在引入的js文件中,对DOM节点进行替换,以CommentBox组件为例:
((window, document) => { ReactDOM.render( <CommentBox limit={20} type={3} id={id} initalLogin={initalLogin}/>, document.querySelector("#comments") ) })(window, document)
对于页面的开发,造成了额外的工作量。
一些React组件初始化的数据,获取不易
React组件初始化时,需要把一些数据作为props传递进去。例如isLogin属性,对于一个有登录功能的网站,是否处于登录状态,影响了组件的展示。但是isLogin这个状态如何拿到呢,我们只能在Nunjucks模板中进行书写:
// repo.njkvar initalData = (function(){ var data = { id: "{ {id}}", initalLogin: { {"true" if currentUser.userId else "false"}} } return function(){ return data } })()
通过initialData这个全局变量获取React组件初始化所需要的props。
不同组件之间的状态互相影响
我们的应用中,有一些状态需要在不同组件间共享。比如登陆状态isLogin,一些应用的做法是弹窗登陆后,强制刷新页面,使各个组件刷新状态。但是强制刷新页面会影响用户体验,这里,产品的需求是这样的:
点击点赞按钮,弹出登录框,进行登陆后,进行主动点赞,其他与登录状态有关的组件,检测到登录状态改变后,进行数据获取和显示刷新。
由于我们的组件,是根据id直接挂在在DOM节点上的,这些组件之间没有嵌套关系,不能通过props去传递状态。只能通过基于发布-订阅者模式的全局事件处理。在每个组件进行登录状态的trigger和监听。组件间需要共享的状态不仅仅只有isLogin,这样可以预见,我们需要在React组件的事件上,绑定大量的全局监听和触发事件。这样增加了组件之间的耦合,不利于代码的维护。
出于上述的考虑,我们选择了使用React进行前后端同构。
什么是同构
同构(Isomorphic)并不是一个新鲜的概念。一些团队已经基于他们的业务实现了同构直出(参考[1])。
这里再简单介绍一下,根据自己理解,同构可以看成,只需要维护一份代码,client side(Browser端)和server side(Nodejs端)都可以共用。
这样,在获取数据后,server side可以返回已经渲染好的html文件,满足SEO需要的同时,相比纯client rendering,也减少了响应时间,对于用户来说,就是减少了白屏这样不好的体验。
之后,前端拿到后端返回的HTML和数据,使用同一份代码,再次进行渲染。
(图片来自网络)
同构方案的选择
有Next.js这样的服务端渲染框架,提供了脚手架,生成同构网站。我们没有直接采用Next.js,主要是出于以下几方面的考虑: