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
这里有两个函数,将 service 层抽象出来。routerDecorator 装饰器用于定义函数的路由信息,initService 将 service 信息初始化到路由中,如果是 GET 请求,将 query 参数注入到 service 中,其它请求会对 query 与 body 参数做 merge 后再传给 service。
前端是个比较苦逼的工种,面临着一年一变的开发框架,一季一变的脚手架,一月一变工具库,这几年现已经发展到整个开发生态圈一年一变。
然而对于新技术的追求是一定要有的,毕竟唯一不变的东西就是变化,在互联网行业跟不上变化就等于淘汰。对于比较有开发经验的前端同学们来说,学习一项新的框架是非常轻松的,积极订阅技术周刊、看文档、逛github都可以使你迅速跟上前端变化的节奏。
回到现实,在大公司的大业务线,比如我所负责的百度贴吧,情况没有那么乐观。一个十多年的业务线所积累的业务代码是每一个个体无法想象,也无法掌控的,贴吧的前端代码几乎反应了整个前端历史的发展轨迹:在体系复杂的基础项目、林林种种的创新项目、变化多样的运营项目中,几乎所有博文中介绍过的优雅,神奇,黑科技的方法毫无例外都被使用过,框架集中在了jquery生态,是jquery时代混合php编程的经典范例。然而随着前端的发展,产品迭代的加速,旧的前端开发架构已经越来越无力。
在前后端分离开发方式早就被实践的今天,想在贴吧做一点点改变也会受到编译脚本、模块耦合,php全环境问题的困扰,任何小小的优化都会牵一发而动全身,于是我们开始了漫长的改造,从制作新的编译脚本,使用新开发流程,对fis通用化定制,以及后端UI层改为nodejs全方位辅助前端模块化开发,框架选用了React。
写到这里,应该总结一些为什么要使用React理由,毕竟前端变化那么快,为什么这么看好React呢?React不仅仅有非常优秀的模块化机制,普通的业务模块也能拆出来拥抱npm,更重要的是推出了虚拟dom思想,提高dom渲染效率,使得跨平台开发成为可能。也许在未来web app会替代native app(假设),可是虚拟dom更使后端渲染成为了可能,web app也需要借助虚拟dom的优势优化首屏用户体验。
Fis3 vs Webpack
fis3是完整的前端构建工具,webpack是前端打包工具,现在fis3也拥有了webpack对npm生态打包的能力,详情参考这篇文章:如何用 fis3 来开发 React?。
让 fis3 拥有 webpack 的打包能力,只需要
fis-conf.js
添加如下配置:假设我们将项目分为
client
与server
,可以按需加载前端用到的文件:再将前端文件使用 typescript 编译:
如果上线后需要将文件发布到 cdn 域名下,可以动态替换,同时开启压缩等操作:
生产环境需要压缩前端文件:
这样就将所有
js
依赖文件都打包到/client/pkg/bundle.js
,css
文件都打包到/client/pkg/bundle.css
,同时fis3会自动替换html中的引用。Yog2 vs express
yog2是基于express封装的nodejs UI层解决方案,文档地址。主要特点使用了app拆分,使得协同开发变得方便。业务项目与node根项目分离,本地开发时,使用fis3的
http-push
能力提交到node根项目子项目文件夹中,保证不同业务项目的分离。先安装yog2:
运行:
让项目上传到 yog2 根项目中,需要修改
fis-confg.js
:支持 bigpipe、quickling,以及后端渲染,默认支持mvc模式,自动路由:
/server/api/user.ts
的default export
默认监听/[project-name]/api/user
这个url。开发中支持热更新,只要添加
--watch
参数,无需重启 node 就可以更新代码逻辑:Fit vs Antd
Fit和Antd类似,是一款基于commonjs规范的React组件库,同时提供了对公司内部业务线的定制组件,不同的是,Fit组件源码使用typescript编写,使得可维护性较强,由FEX团队负责维护(现在还未对外开放)。
除了提供通用的业务组件以外,还提供了同构插件 fit-isomorphic-redux-tools,这个组件提供了基于redux的同构渲染方法支持。
React 后端渲染企业级实践
先从业务角度解析一遍后端渲染准备工作,之后再解析内部原理。
后端模板的准备工作
对纯前端页面来说,后端模板只需要提供基础模板,以及各种
api
接口。为了实现后端渲染,需要根据当前(html5)路由动态添加内容放到模版中去,因此fit-isomorphic-redux-tools
提供了封装好的serverRender
函数:server/action/index.ts
server/router.ts
从
server/router.ts
说起,引入了 service(下一节介绍),对非/api
开头的 url 路径返回server/action/index.ts
文件中的内容。server/action/index.ts
这个文件引用了三个client
目录下文件,分别是routes
路由定义、basename
此模块的命名空间、rootReducer
redux 聚合后的 reducer。读取了client/index.html
中内容,最后将参数全部传入serverRender
函数中,通过enableServerRender
设置是否开启后端渲染。如果开启了后端渲染,访问页面时,会根据当前路由渲染出对应的 html 片段插入到模板文件中返回给客户端。在后端抽象出统一的 service 接口
server/service/index.ts
fit-isomorphic-redux-tools
还提供了两个工具initService
export 出去供 router 绑定路由,routerDecorator
是个装饰器,第一个参数设置 url 地址,第二个参数设置 httpMethod。定义一个 Service 类,每一个成员函数都是对应的后端 api 函数,支持同步和异步方法。最后创建一个 Service 的实例。当通过 http 请求访问时,同步和异步方法是没有任何区别的,当请求从后端执行时,不会发起新的 http 请求 ,而是直接访问到这个函数,对异步函数进行异步处理,使得与同步函数效果统一。
自此后端模块介绍完毕了,可以对 service 进行自由拆分,例如分成多个文件继承等等。
前端模板文件处理
client/index.html
引入
mod.js
是为了支持 fis 的模块化寻找(webpack将类似逻辑预制到打包文件中,所以不需要手动引用),index.tsx
是入口文件,需要通过fis-conf.js
设置其为非模块化(仅入口非模块化),之后都是模块化引用:window.__INITIAL_STATE__ = __serverData('__INITIAL_STATE__');
这段代码存在的意义是,后端渲染开启时,会替换__serverData('__INITIAL_STATE__')
为后端渲染后的内容,在 redux 初始化时传入window.__INITIAL_STATE__
参数,让前端继承了后端渲染后的 store 状态,之后页面完全交给前端接手。前端入口文件处理
client/index.tsx
fit-isomorphic-redux-tools
提供了方法routerFactory
返回最终渲染到页面上的 React 组件,第一个参数是路由设置,第二个参数是项目命名空间(字符串,作为路由的第一层路径,区分子项目),第三个参数是 redux 的聚合 reducer。routes 是非常单一的 react-router 路由定义文件:
client/routes.tsx
reducer也是基本的 redux 使用方法:
client/reducer.tsx
config 文件是定义文件,将静态定义内容存放于此:
client/config.tsx
action、reducer
存放在 stores 文件夹下. actions 可共用,但对于复杂项目,最好按照 state 树结构拆分文件夹,每个文件夹下对应
action.tsx
与reducer.tsx
。将 Redux 数据流与组件完全解耦。特别对于可能在后端发送的请求,可以使用
fit-isormophic-redux-tools
提供的fetch
方法:client/stores/user/action.tsx
然后在前端任何地方执行,它都只是一个普通的请求,如果这个 action 在后端被触发(比如被放置在 componentWillMount生命周期中),还记得 service 中这段代码吗?
会直接调用此方法。第一个参数是 params(get) 与 data(post) 数据的 merge,第二个参数是 req,如果在后端执行此方法,则这个 req 是获取页面模板时的。
组件
使用
connect
将 redux 的 state 注入到组件的 props,还不熟悉的同学可以搜一搜 react-redux 教程。组件的介绍为什么这么简单?因为有了
fit-isormophic-redux-tools
插件的帮助,组件中抹平了同构请求的差异。再次强调一遍,在任何地方调用 action ,如果这段逻辑在后端被触发,它会自动向 service 取数据。fit-isomorphic-redux-tools 剖析
核心函数
serverRender
代码片段renderFullPage
方法,返回页面模板,可接收参数将后端渲染的内容填入其中,如果不开启后端渲染,无参调用此方法即可。为了抹平前端请求在后端处理的差异,需要触发两次
renderToString
方法,上述代码是第一次。因为fetch
方法在前后端都会调用,我们将serverRequestHelper.Request
传入其中,当 action 在后端执行时,不会返回数据,而是将此 action 存放在Map
对象中,渲染完毕后再将 action 提取出来单独执行:因为 react 渲染是同步的(vue2.0 对此做了改进,可谓抓住了 react 的痛点),对异步操作无法处理,因此需要多渲染一次。这时,redux 的 store 中已经有了动态请求所需的数据,我们只需要再次渲染,就可以获取所有完整数据了:
核心函数
promise-moddleware
代码片段篇幅原因,默认大家了解 redux 中间件的工作原理。这里有个约定,action 所有异步请求都放在 promise 字段上,dispatch 分为三个状态 (_REQUEST,_SUCCESS,_FAILURE)。前端请求都是异步的,因此使用
promise.then
统一处理,后端请求因为直接访问 model ,异步时,与前端同样处理,同步时,直接调用 promise 函数获取结果。还记得server/service/index.ts
文件中为何能支持普通方法,与async
方法吗?因为这里分开处理了。核心函数
service
代码片段这里有两个函数,将 service 层抽象出来。
routerDecorator
装饰器用于定义函数的路由信息,initService
将 service 信息初始化到路由中,如果是GET
请求,将 query 参数注入到 service 中,其它请求会对 query 与 body 参数做 merge 后再传给 service。总结
React 组件生态降低了团队维护成本,提高开发效率,同时督促我们开发时模块解耦,配合 redux 将数据层与模版层分离,拓展了仅支持 view 层的 React。后端渲染大大提高了首屏效率,大家可以自己规划后端渲染架构,也可以直接使用
fit-isormophic-redux-tools
。目前来看,React 后端渲染的短板在于
RenderToString
是同步的,必须依赖两次渲染才能方便获取异步数据(也可以放在静态变量中实现一次渲染),对于两层以上的异步依赖关系处理起来更加复杂,这需要 React 自身后续继续优化。当然,任何技术都是为了满足项目需求为前提,简单的异步数据获取已经可以满足大部分业务需求。webpack只是个打包工具,我们不要过分放大它的优势,一个成熟的业务线需要 gulp 或者 fis3 这种重量级构建工具完成一系列的流程,如今 fis3 已经支持 npm 生态,正在不断改造与进步。对 express 熟悉的同学,转到企业开发时不妨考虑一下 yog2,提供了一套完整的企业开发流程。
如有遗误,感谢指正。
线上项目
http://tieba.baidu.com/n/tbhighh5/album/456900832689737361 ,开启后端渲染
http://tieba.baidu.com/n/tbhighh5/album/456900832689737361?nsr=1 ,关闭后端渲染
The text was updated successfully, but these errors were encountered: