微信錢包內的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到家技術團隊向前端工程化、先後端分離邁出了標誌性的一步。後續須要作的事情還不少,不論從項目自己,仍是從團隊總體架構的角度,都有很大的進步空間。也歡迎各位同行提出意見和建議。