微信钱包内的58到家全新首页已经上线,感兴趣的同窗们能够在微信中打开“个人->钱包->58到家”查看。javascript
58到家全新首页提出重构主要是为了解决如下问题:css
58到家目前两年左右的发展期,整个技术生态还不完善。以上的问题有的是因为创业初期遗留的历史缘由形成,好比代码写死和粗糙的配置后台;而有的问题是由落后的开发模式和协做模式形成的,好比先后端分工不明确、首页加载速度慢。html
基于上文提到的问题,重构从如下几方面入手:前端
配置后台能够理解为一个简易的CMS系统,配置的内容是一些量化的字段,好比图片地址、连接、价钱等等。此项目中本人并不负责配置后台的开发,因此再也不班门弄斧。vue
下面详细描述重构过程当中前端的解决方案。java
根据上文提到的问题,此项目中前端的技术选型以下:webpack
看到以上的技术选型,可能会有读者疑惑:不就是一个前端模板+模块化方案吗,有什么值得介绍呢?git
首先,以上的技术选型的背景以下:github
目前58到家的先后端协做模式仍然很原始,本次重构采用的先后端分离方案并不是是最优解,只能算是一种折中的过渡方案。总结有如下几点:web
下面分别简单描述以上的几点。
tpl的内容以下:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no"> <title>58到家</title> <link rel="stylesheet" href = "http://static.daojia.com/assets/project/wx_index/style/main.wx-index.css?2016082001"> </head> <body> <div class="window"> <app :data="data"></app> </div> <script type="text/javascript"> // initial page data var pageData = {}; </script> <script type="text/javascript"> // for traker var bi_params = {pagetype:'activity'}; </script> <script src="https://static.daojia.com/bi/buried_point/js/tracker.js"></script> <script src='http://static.daojia.com/assets/common/js/zepto.min.js'></script> <script src='http://static.daojia.com/assets/common/js/vue.min.js'></script> <script src = "http://static.daojia.com/assets/project/wx_index/js/main.wx-index.js?2016082001"></script> </body> </html>
tracker.js
、zepto.min.js
、vue.min.js
是依赖的第三方文件,全局变量bi_params
是bi统一用的初始字段;
<app :data="data"></app>
是vue组件的容器;main.wx-index.js
和main.wx-index.css
的时间戳;pageData
。这是首屏的初始数据,之因此选择以全局变量的方式暴露,而不是请求api,是为了减小一次http请求,尽快渲染首屏。tpl轻量化是为了减小FE和RD的耦合,固然最佳的方式是tpl交由FE维护,可是目前58到家的开发模式并不适合。因此采用了折中的过渡方案。
使用url query做为js和css文件的缓存策略也并不是最优解,理想的方案是使用hash指纹。可是hash指纹须要FE在编译完成以后将hash值告知RD,而时间戳能够任意修改为与当前不一样的值便可,减轻了沟通成本和失误率。
选择客户端渲染有如下几个优势:
固然客户端渲染也有一些缺点,好比:
具体到客户端渲染的技术选型,其实从实现功能上来说随意选用一种js模板工具便可,好比artTemplate。最终选择vue的缘由有如下几点:
这次重构采用的缓存策略仍然比较原始,好比前文提到的url加query的方式。这也是后续有待优化的一个重点。
58到家首页的内容很是多,大部分尺寸的手机须要三屏才能加载完成。一次性加载的用户体验确定不太顺畅,按照主流的手机尺寸,将整站分红三部分:首屏+次屏+尾屏。基本的加载流程以下图:
简单归纳以下:
main.wx-index.js
根据首屏json渲染首屏;wx-index.themes.js
的加载;wx-index.themes.js
加载成功后发起jsonp请求次屏和尾屏数据;wx-index.tail.js
的加载;wx-index.tail.js
加载成功后渲染尾屏;次屏的wx-index.themes.js
和尾屏的wx-index.tail.js
是按需加载的,是为了减小首屏的请求数和数据体积。
按需加载功能使用require.ensure
API实现。之因此选择使用它有如下几点考虑:
require.ensure
API的支持,不须要额外的模块化工具;wx-index.themes.js
的themes
,wx-index.tail.js
的tail
),而require.ensure
则能够定义模块名称,使文件名更语义化;下面简单介绍一下如何结合vue和require.ensure
实现按需加载和动态组件。
require.ensure
实现按需加载和动态组件回顾上文的tpl代码能够看出,页面总体是一个vue组件。顶层组件是<app></app>
。首屏组件是第一级子组件,次屏是第二级子组件,尾屏是第三级子组件。总体结构以下图:
vue实现按需加载动态组件要考虑如下几点:
对比上图能够看出子组件容器的位置:
<div class="main"> <!-- activity banner --> <wx-activity v-if="data_activity" :data="data_activity"></wx-activity> <!-- nav --> <wx-nav v-if="data_nav&&data_nav.length!==0" :data="data_nav"></wx-nav> <!-- headline --> <wx-headline v-if="data_headline&&data_headline.length!==0" :data="data_headline"></wx-headline> <!-- service --> <wx-service v-if="data_service" :data="data_service" :test="test"></wx-service> <!-- fresh --> <wx-fresh v-if="data_fresh" :data="data_fresh"></wx-fresh> <!-- banner --> <wx-banner v-if="data_banner&&data_banner.length!==0" :data="data_banner"></wx-banner> <!-- themes --> <div class="wx-index__themes"> <themes></themes> </div> <wx-footer :notice="data.showReddot"></wx-footer> </div>
<template> <template v-for="theme in data.themes"> <slider v-if="theme.type==='slider'" :data="theme"></slider> <single-slider v-if="theme.type==='singleSlider'" :data="theme"></single-slider> <list v-if="theme.type==='list'" :data="theme"></list> <grid v-if="theme.type==='grid'" :data="theme"></grid> </template> <div class="tail"> <tail></tail> </div> </template>
wx-index.themes.js
加载成功,在渲染Themes组件以前须要请求次屏的数据,jsonp请求放在vue组件的activate
钩子函数内:
activate: function(done) { let _this = this; let _url = '/home/ajaxGetSecondIndexPage'; let _cityId = pageData.cityId||$.fn.cookie('comm_cityid'); let _openId = pageData.openId||''; $.ajax({ url: _url, data: { cityId: _cityId, openId: _openId }, dataType: 'jsonp', success: function(res){ if(!res||!res.data){ return; } _this.data = Object.assign({},res.data); // 将底部固定模块的数据写入全局变量,以便懒加载所需 window.dj_index_data_tail = Object.assign({},{ layidle: _this.data.layidle, recommend: _this.data.recommend }); }, complete: function(){ done(); } }); }
vue组件在
activate
钩子函数返回done()
以后才会继续执行后续工做。
请求成功以后将返回的数据赋值给vue组件的data
,而后vue根据data
渲染UI。
上述代码有一点须要注意。你们看到代码将一些数据赋值给了全局变量window.dj_index_data_tail
,这些数据是尾屏的数据。因为尾屏的数据量比较小,因此与次屏的数据合并成一个API。这个全局变量是为了尾屏的Tail组件渲染使用。这就是上文提到的“组件数据如何传递”。
使用全局变量传递数据的方式当然不是很优雅,可是不失为一个适合快速开发的方案。这也是后续迭代的优化点之一。
次屏渲染完毕以后触发尾屏的加载,这个行为实在Themes组件的ready
钩子函数内进行,以下:
ready: function(){ let loadTail = function() { if(!window.isTailLoaded){ // 主题推荐位渲染完成以后加载底部模块 require.ensure([], function(require) { require("../../_tail.js"); }, 'tail'); window.isTailLoaded = true; } }; loadTail(); }
因为以前将Tail组件的数据储存在全局变量中,Tail组件的activate
钩子函数内能够直接读取次全局变量:
activate: function(done){ this.data_layidle = window.dj_index_data_tail.layidle&&Object.assign({},window.dj_index_data_tail.layidle); this.data_rec = window.dj_index_data_tail.layidle&&Object.assign({},window.dj_index_data_tail.recommend); done(); }
以上,便完成了整个页面的按需加载流程。固然,每种方案都不是最优解,但都是适用于目前状态的一种比较好的方案,后续迭代中持续优化。
开发环境的模块化方案比较随意,借助于boi框架中的babel模块,能够将新规范的语法编译为浏览器适用的语法。
这次重构的开发环境的模块化开发使用的是ES6 Modules,语法简洁易懂,而且开发环境没有加载动态模块的需求,静态的ES6 Modules彻底适合。
插播广告:58到家前端工程框架boi支持多种模块化方案,包括ES6 Modules、CommenJS和AMD。
依前文所述,本次重构中的仍然有不少问题,这些问题是后续迭代中急需解决的。总结以下:
58到家微信钱包重构项目告一段落,其中采用的诸多解决方案中有好的也有很差的。不过整体来讲,这次重构中58到家技术团队向前端工程化、先后端分离迈出了标志性的一步。后续须要作的事情还不少,不论从项目自己,仍是从团队总体架构的角度,都有很大的进步空间。也欢迎各位同行提出意见和建议。