2016前端探索總結——前端工程與未來

2016年彈指一揮間,轉眼間已到了自己學習web的第三個年頭。在過去的這一年,前端領域發生了翻天覆地的變化——

  • Angular1.x慢慢退出舞台,Angular2、React、Vue等新興的MVVM框架已接近成熟。經過一年的迭代,API與底層設計思路上有趨同之勢,但也慢慢顯現出在各自領域中的優勢。
  • 以Redux和Vuex為代表的類Flux設計架構,經過一年的探索和實踐,其在開發效率和維護追蹤上的優勢已被驗證,取代MVC成為了中大型web應用中最流行的框架模式。
  • Webpack、Rollup等包管理工具已是工程必備,ES6和Babel普及度已經很高了,前端開發完成從頁面到組件化模塊化、從解釋型到構建型的工程性轉變。

寫這篇文章的初衷,一是由於這一年中也大大小小做過好幾個項目,對於前端工程有一些淺薄的理解和經驗,想要成文記錄下來;二是結合已有的知識,暢想一下未來web發展的趨勢。

關於Flux的經驗思考

何為Flux不再過多贅述,不懂的讀者推薦這篇文章圖解Flux。

說白了,Flux就是一種新的單向數據流的「視圖綁定式」解決方案。這裡我個人加了「視圖綁定式」這個定義,是因為只有在View-Model綁定的基礎上,Flux才能充分發揮它的作用。

上圖是Flux的架構圖。簡單地說,Flux將視圖變成了狀態機,所有對視圖的操作都轉變為對Store的狀態操作。狀態的更新會通過各種框架的映射機制反應到視圖上。而在具體的實現中(Redux,Vuex),Store往往被設置成全局的單一狀態樹。這樣一來,各組件、各模塊之間將不存在任何數據交互關係,完全解耦。所有的模塊或組件只做三件事:接收用戶在視圖層的指令,更改全局Store樹,從Store中獲取各組件的狀態。

由於沒有基於Redux做過工程方面的實踐,所以這裡重點說一下Vuex,不知道Vuex的同學可以戳這裡Vuex。(這裡要特別安利Vue.js這款非常棒的漸進式框架,本身很輕,適合小型應用的開發,同時又有構建大型應用的解決方案)

(Vuex架構圖)

個人認為,利用Vuex,與傳統的MVVM架構相比,對於構建中大型項目有如下優勢——

  1. 很好地解決了父子組件數據級聯與組件間的通訊問題。在傳統的MVVM模式下,我們通常採用事件機制進行跨組件的數據傳輸,這種做法的一大壞處就在於組件之間產生耦合。在中大型規模的項目中,常常有上百甚至上千的組件(像這學期的學雷鋒項目教學輔助應用,算是中小型規模,就已經有將近50個組件),組件間的交互將變得極其複雜和難以追蹤,導致開發和維護成本非常高。而在Flux,確切地說是Vuex中,組件的所有動作都是發送事件,調用全局的action(action本質上是訂閱的回調函數)。同時,組件的狀態也是由全局的state樹派生出來,並與其綁定。如此,所有組件對視圖的更改,都轉變為對全局狀態的更改,從而避免了組件間的通訊。
  2. 方便追蹤應用數據流。前面我們提到,級聯的跨組件通訊帶來了很強的耦合性,不僅如此,還使得數據流難以追蹤。在使用MVC架構的中大型規模web項目中,前端往往一次性要存儲和展示幾十甚至上百種數據,牽扯到好幾種Model,並且這些Model之間一定是「糾纏不清」的。這時候一旦出現Bug,很難去進行Bug定位。而Vuex中數據流是單向的,事情就簡單很多——首先,定位出現Bug的相關視圖和操作;然後,根據視圖分析出相關State->Mutations->Actions(過程和數據流方向是相反的);最後,通過Actions和視圖操作找到出問題的組件。(如果你嚴格按照模塊化或組件化的模式來組織代碼,那Debug效率又會提升很多。)
  3. 方便預測和回溯應用狀態。前面也提到,Flux架構下的應用實際是一個狀態機,而整個數據流是單向的,因此我們很容易根據一個Action便預測出應用下一階段的狀態。同時,在做「撤回」、「重做」等類似回溯的功能時,也可以很輕易地通過保存和提取應用在某一階段的狀態來實現。另外,Vuex中的Mutations專門用於更改State,官方建議是同步操作放在Mutations(參考這篇答案),這樣的好處是每當調用Mutations便可同步獲得一個新的應用狀態,方便做snapshot。

但任何框架或架構的引入,都必須基於對於項目類型和項目規模的考量之上。Flux尤擅於數據類型繁多,交互密集的應用,但對於數據類型較少、交互不多的應用(例如H5,展示型網站等),就不適用了。

另外,儘管是在中大型項目中,嚴格遵循Flux有時也會把一些問題複雜化,例如下面的.vue模板代碼:

<template>n <div>n <v-loading v-if="isLoading"></v-loading> //一個loading動畫n </div>n</template>n<script>n import {mapState} from "vuex";n export default {n data(){n return{n isLoading:false //局部state的形式n }n },n computed:mapState(state){ //引入全局state的形式n isLoading:state=>state.isLoadingn },n methods:{n someFunction(){n isLoading=true; //第一種方式n //this.$store.dispatch(isloading,true) //第二種方式n ...n }n }n }n</script>n

上段代碼中使用了兩種引入state的模式,一種是組件自身的局部state,由data進行初始化;另一種是嚴格遵守Flux,從全局Store中提取的state。如果我們使用第二種方式更改一個動畫loading,那我們需要專門像下面的代碼一樣編寫actions,mutations:

export const loading=({commit},signal)=>{n commit(loading,signal);n}n

const state={n isLoading:false; //loading的全局state狀態n}nnconst mutations={n loading(state,signal){n state.isLoading=signal; //更改staten }n}n

當loading比較多的時候,會顯得很麻煩。而如果採用局部state的方式,我們只需要在methods中更改狀態即可。但問題在於,actions中常常有非同步事件(如AJAX),需要在非同步完成後調整loading的狀態,根據單向數據流的原則,看上去只能通過修改全局的state來達到目的。

ES6提供了一個非常好的工具——Promise。具體請參照Vuex文檔。如此我們便可以做到類似如下的調用:

...n },n methods:{n someFunction(){n this.$store.dispatch(loading,true).then(n ()=>{ //成功回調n isLoading=true;n }, n ()=>{} //失敗回調n )n }n }n}n

如此一來便能在Vuex的架構下充分利用局部State管理視圖,不僅可以提高開發效率,也有利於保持全局State的簡潔。

框架與工具的思考

尤大在文章"Vue2.0 漸進式前端解決方案"里對工具框架引入的考量作了非常恰當的比喻。

工具複雜度是為了處理內在複雜度所做的投資。為什麼叫投資?那是因為如果投的太少,就起不到規模的效應,不會有合理的回報。這就像創業公司拿風投,投多少是很重要的問題。如果要解決的問題本身是非常複雜的,那麼你用一個過於簡陋的工具應付它,就會遇到工具太弱而使得生產力受影響的問題。

這裡的複雜度,我認為是針對特定團隊而言的。比如你的團隊對React全家桶有豐富的實踐經驗,那麼在為相同的項目選型時,React的複雜度於你的團隊和於其他團隊是不同的。但這麼看其實是很虛的,原因是很少有人能準確地評估應用內在複雜度和工具複雜度。

經過觀察,通常情況是高估內在複雜度,低估工具複雜度。所以現階段我總結了一條不是很正確,但起碼不會錯的方法論:以最佳實踐為基礎,再增量考察和評估額外的工具引入成本。

舉兩個栗子:

  1. 對於非常輕的頁面,如H5、活動頁面,最佳實踐一般是Jquery或zepto+UI庫。但現在很多輕頁面開始有一些數據交互的需求,需要考慮引入一些MVVM框架,這時候再增量評估引入React、Vue這些View層框架的複雜度及應用內在複雜度,而不是把Jquery或Zepto和它們放到同一地位來進行比較考量。

  2. 開發單頁面應用,一般來說是以數據交互為主的需求。Vue全家桶的最佳實踐是Webpack+Vue(單文件組件解決方案)+Vue-router。React全家桶的最佳實踐是Webpack+React+React-router。以此為基準,綜合項目預期成本,再考慮加入UI庫、Babel、Redux(Vuex)帶來的額外複雜度和消弭應用內在複雜度之間的平衡關係。

這樣的好處是基本跳過最佳實踐的複雜度評估,直接對風險較高、把握較低的複雜度部分進行評估,節省了決策成本,原因有二:

  • 一般來說,最佳實踐是在各種工具的組合下,複雜度最低的方案。
  • 最佳實踐本身包含了前人的決策經驗和教訓,沒有必要花過多時間來做重複地評估決策。

另外,以上所說的「最佳實踐」,有兩點需要注意的地方:

  1. 重點不是在best,而是在experience。不要陷入了找尋最優解的泥潭。

  2. 「最佳」不是絕對概念,而是相對概念。一定要針對個人(團隊)的情況來考慮何為「最佳」。

前端工程化的思考總結

下圖展示了整個前端工程可能涉及到的各個環節,前端技術體系大局觀一文覆蓋式地簡要介紹了各層系統的職能。

這裡我結合自身的知識和實踐經驗,列出一些實現成本並不大並且性價比很高的工程化方案:

  • 網路性能優化:現在的web應用,大小動則上MB,加上一些靜態資源,在目前我大天朝的網路環境下,在不做優化的基礎下達到類似「首屏3s」或「動態載入xxs」的非功能性需求,需要付出很昂貴的硬體成本。目前網路優化主要有兩個思路:CDN加速和緩存策略。對於前者,大公司往往採取自建CDN節點的方式,而小公司可以使用類似七牛雲這樣的雲儲存來進行靜態資源的加速。而緩存策略則有許多的解決方案,有時還會牽扯到部署、迭代的問題。我的另一篇文章介紹了前端如何利用Webpack和伺服器的緩存機制來解決應用緩存與增量更新的問題。(推薦這篇回答大公司里怎樣開發和部署前端代碼?)
  • 流量統計:這一塊市面上已有非常成熟的方案,如CNZZ、百度站長統計等。當然也可以自己做一套,難度並不大,基本原理是在頁面中拼裝出<script>標籤,再遠程載入javascript。
  • 自動化部署系統:這個系統可以包含倉庫代碼管理、一鍵部署、一鍵Pull/Push,具體可以針對業務定製。它可以簡化web應用從構建到上線的流程,提高效率,在快速迭代的web項目中非常有用。一個實現思路是利用後端服務調用預先編好的Shell腳本來完成命令行集操作,實現自動刪除舊代碼,clone新代碼,安裝環境等行為。
  • 代碼質量監控:可選的工具有JSLint和JSHint。前者對代碼要求極為嚴格,後者對代碼要求可以很寬鬆。Webstorm和PHPStorm已內置兩種工具,個人推薦使用JSHint。

除此之外,還有諸如日誌系統、監控預警、自動化測試、API測試等細節領域,我還在探索階段,這裡就不過多贅述了~

Web前端的未來

我認為一種技術的未來前景可以從這三個維度來分析:全新體驗、成本下降趨勢、市場潛力。

  • 全新體驗:即帶給用戶一個前所未有的體驗。從需求角度來說就是幫助用戶塑造全新的預期,並做持續性的滿足。
  • 成本下降趨勢:這裡的成本,不僅包括開發人員的人時成本、維護成本、工具鏈成本等,還包括可能遺留的技術債務,未來「還清債務」的成本。
  • 市場潛力:評估某項技術的市場潛力,可以看以這項技術為核心的行業在未來的市場容量。

從這三個維度出發,我們不難發現web未來發展的三個可能趨勢:

  1. WebVR:WebVR是一塊全新的領域,能夠在低成本設備上塑造VR的體驗,同時也是高端平台快速瀏覽、分發內容的一種形式。早在2016年上半年,Mozilla就攜手一眾廠商完成了WebVR標準的制定,並在年底發布了Mobile、Oculus Rfit、HTC vive三種平台的解決方案Mozilla VR。同時,優秀了很久的3D引擎庫three.js,也將成為WebVR的重要技術基石。
  2. 大前端:@向昶宇向總在我浙INA的一次內部分享中也提到了對「大前端」的看法,我非常贊同。 Web,準確地說是Javascript,將會成為web應用、移動端Native應用、桌面應用、智能設備應用、智能硬體(沒錯,JS已經能用來寫硬體了——Ruff)的一種更廉價的替代技術。在未來,也許你的公司不再需要同時組件Web、IOS、Android三支開發隊伍,取而代之的是一支大前端團隊。

  3. WebRTC:目前各大網站的視音頻通訊和遊戲都依賴於Flash,而Flash在未來兩年應該會被全面淘汰。WebRTC被瀏覽器原生支持,實現了瀏覽器之間P2P的實時數據傳輸,同時是目前對於瀏覽器來說唯一一個可以不用Flash與外界使用UDP進行數據交換的技術,在流量節省和擁塞控制上具有很大的優勢。隨著各大瀏覽器實現對WebRTC的支持(目前只有Chrome、Firefox和Opera),在未來它一定會成為網路音視頻和在線遊戲實時數據傳輸的主要解決方案。

延伸閱讀

2016-我的前端之路:工具化與工程化

每個架構師都應該研究下康威定律

[譯]WebVR 中的「共同在場」

未經允許,禁止轉載


推薦閱讀:

用 husky 和 lint-staged 構建超溜的代碼檢查工作流
2.1 前端工程化概述
如何將一個已經上線的項目前端部分平滑過渡至組件化和工程化?
如何做一個100+模塊的前端項目開發設計管理?
大公司里怎樣開發和部署前端代碼?

TAG:前端开发 | 前端工程化 | 互联网 |