在當今的前端開發領域,大紅大紫的元件化開發如萬人空巷,前端技術圈中關於元件化討論的文章亦如汗牛充棟。然而別人的理解終歸是別人的,作為一個胸存小志的開發者,我還是希望能夠根據自己的理解和實際工作,總結自己對元件和元件化開發的認知。
在我第一次接觸元件化概念時,一時迷迷糊糊,如墜雲霧深處。元件是什麼?元件化開發是什麼?為什麼大牛們知道這麼多而我不知道?這應該並不是我個人的疑問,每一個除此接觸概念的新手,都會有此疑惑。
為什麼大牛們知道這麼多而我不知道?
我曾經無數次為類似的問題而煩惱,也曾感到急躁難耐。回答這個問題,我們需要有一個基本認知:任何一個新概念都是在無數前輩先賢的實踐、總結中繼承發展而來的。元件化開發也不例外。這個問題涉及認知學,能夠引出很多值得探討的問題,但這並不是本文的重點。關於前端元件化的發展過程,我推薦xufei大神的這篇文章:Web應用的元件化(一)——基本思路。
元件化開發是什麼?
原來架構設計比較多關注的是橫向的分層,即資料層,邏輯層和UI層。而元件化架構必須同時關注縱向的隔離和解耦。在分層和分模組後,每一個業務元件由三層各自存在的部署包組成,包本身是一個包含了技術元件和服務元件的一個結合體。由資料層,邏輯層,介面層三層的三個業務包可以構成一個完整的具備獨立功能的業務元件。【人月神話的部落格】
這個解釋很正確,但太概念化,我理解的元件化開發是將複雜並混亂的頁面邏輯,分割成一個個獨立的業務單元。
元件是什麼?
根據上面的回答,我們基本可以確定,元件就是頁面中一個個獨立的邏輯單元。這個結論是放之四海而皆準的,然每一個高高在上的理論都要落地,根據具體情況具體回答。元件放到前端就要有一個符合前端技術的回答:前端元件就是模板、樣式、程式碼邏輯相結合的、獨立的、可複用的業務邏輯單元,其中模板由html承擔、樣式由css負責、程式碼邏輯由JavaScript編寫。
由張雲龍大神的這張圖,可以看出元件化的基本意圖以及元件的基本組成。
任何一種新的開發方式,都不能靠只讀幾篇文章就能明白,必須要實際動手並在工作中有所總結,才能徹底掌握。所以我並不奢望靠上文的幾段文字就能讓讀者完全明白元件與元件化開發。
接下來我將根據自己實際的開發經驗,與大家分享一下我對元件的認知的和經驗總結。
元件的基本修養
任何一個華麗的思想都有一套樸實的程式碼實現。上面我們從抽象的層次上談了談元件的概念,放到實際的程式碼世界,該如何去實現呢?眾所周知,JavaScript是一門物件導向語言,面嚮物件語言最重要的特性就是——抽象。放到實際開發中就是定義一個基類,應用的我們現在的場景,我們需要一個元件基類——Component。由這個基類來提供元件的基礎功能。具體都應該有什麼方面的基礎功能呢?別急,這個問題先放一放。
元件的管理
先看一下上面的這張圖,我們會發現,整個頁面都是由不同的功能的業務元件組成的。這就引出了另一個問題,當一個頁面的元件非常多時,我們需要一套統一管理的倉庫——CRepository。每一個元件都要將自身id向倉庫註冊,倉庫提供管理功能,如增刪改查。具體的方法由實際應用而異,但幾個通用的方法可以參考:
1 2 3 4 5 6 7 |
count: Number.//整個app中元件的數量 add: function(component){....} //將一個元件新增到倉庫中 remove: function(id){....} //將一個元件從倉庫中移除 byId: function(id){....} //根據id從倉庫中查詢元件 |
瞭解完倉庫之後,我們便可以將主要精力放回到Component上了。
元件的生命週期
生命週期這個概念最早在軟體工程中接觸到,可惜我對那些枯燥的理論沒有什麼興趣,上起課來雲裡霧裡,早就還給教授了。那我就舉一個大家都有體會的例子。元件如人,人的生命有盡頭,元件的生命必然有。將元件的生命週期分割成不同的幾個階段來處理不同的邏輯,就如同人的一生不同階段要面對不同的煩惱一樣。
1 2 3 4 5 6 7 8 9 10 11 |
constructor:function(){} //建構函式,處理外部引數 mixinProperties:function(){} //在這個階段,混入必要的屬性 parseTemplate:function(){}//在這個階段解析模板,將模板由字串轉化成dom節點 postCreate:function(){}//在這個階段,模板解析完畢,可以訪問component的根節點cRoot。此時可以對元件的dom樹進行訪問或繫結事件。但此時元件還未加到頁面dom樹中。 startup:function(){}//此時元件以加入dom樹中,這裡可以在元件加入頁面dom後做一些初始化工作。對於巢狀元件,需要處理子元件的startup destroy:function(){}//元件生命結束,進入銷燬階段,從元件倉庫中登出 |
凡是比喻就一定有失真的地方,元件的生命當然不可能與人相比,但我卻發現上面的生命週期與嬰兒從被懷孕與誕生的過程極其相似。
1 2 3 4 5 6 7 8 9 10 11 |
constructor:function(){} //受精卵狀態 mixinProperties:function(){} //染色體重組 parseTemplate:function(){}//嬰兒在母體內的生長髮育過程 postCreate:function(){}//嬰兒在母體內生長髮育完成,母親即將臨盆 startup:function(){}//嬰兒出生,被社會認可 destroy:function(){}//個體消亡,取消社會戶籍等等 |
元件的屬性訪問器
對於元件內部資料的訪問,應當對外提供統一的訪問渠道,通常來講這部分內容就是屬性的取值器與賦值器(get和set)。
1 2 3 |
set(prop, value)//為元件的某個屬性賦值 get(prop)//為從元件中取得某個屬性值 |
要明確的一點是,這裡的set與get不僅僅像點語法一樣單純的賦值與取值,否則就是畫蛇添足。使用過C#的兄臺知道,C#中存在“屬性的Get與Set”,它們能夠避免直接對欄位進行訪問,這裡提到元件的get與set應當具有同樣的功能,具體的實現方式敬請關注後續文章。
元件的模板解析
遇到模板通常會遇到資料繫結的需求,可能是雙向繫結也可能是單向繫結。雙向繫結如眾多的MVVM框架,模板解析過程中可能會讀取元件內資料來渲染dom元素,亦或者元件dom樹生成後,dom元素的變動即可作用於元件內部資料。單向繫結常出現在MVC框架中,如dojo,只是將dom元素與元件內部某個屬性繫結,或者將互動事件與元件內部方法繫結。
JavaScript中沒有註解特性,所以眾多繫結功能都是在template中新增自定義特性,並在解析過程中處理自定義特性。
說到事件的繫結,事件帶來的記憶體洩露問題不容忽視。這就要在元件銷燬時,一併處理元件內部繫結的事件。包括在模板中繫結的事件與元件內部手動繫結的事件。
元件關係
當一個頁面變得越來越複雜時,元件之間必然會出現巢狀。巢狀意味會出現父子關係、兄弟關係等。巢狀的管理可以參照DOM中的層級關係,提供相應的處理方法。但通常來講,只需要管理好父子關係即可,兄弟關係的管理往往太複雜,而且通常情況下,一個getChildren,然後根據索引便能滿足需求。所以大部分類庫中元件關係的管理,往往只需要兩個方法:
1 2 3 |
getParent:function(){}//獲取元件的父元件 getChildren:function(){}//獲取元件內部所有子元件 |
元件通訊
元件變得複雜增多時,另元件之間如何通訊的問題便被應當被提上議事日程。JavaScript本身便適用於訊息驅動,處理元件間的通訊當然要就地取材,事件機制便是最佳方案,所以前端元件應當在事件機制(往往是語義事件)的基礎 提供通訊功能。元件應當既可以接收事件也可以傳送事件,於是應當分別提供方法:
1 2 3 |
on:function(component, eventName, handler) //用於繫結元件事件 emit:function(eventName, event) //元件對外傳送事件 |
元件的銷燬
元件的銷燬屬於元件生命週期的一部分,當元件功能變得複雜,元件正確合理的銷燬就變得尤為重要。元件的銷燬通常要考慮以下幾個方面:
- 元件內部事件的解綁
- 元件dom的銷燬
- 元件內部屬性的銷燬
- 子元件的銷燬
- 元件登出
元件的解析
如果所有的元件都要通過new class的方式去手動初始化,這本無可厚非,然而在現今標籤化語言盛行的時代,是否能夠有一種更為方便的開發方式,將自定義元件也能夠以標籤化的方式來書寫。答案是肯定的,主流的類庫對此一般有兩種做法:一種是完全的自定義標籤方式,如angular2;一種是以自定義標籤特性的方式,如dojo等。所有的這些都需要基礎庫能夠提供元件解析的功能。
通常的思路是以深度優先搜尋的方式,掃描整個DOM樹,解析自定義標籤、自定義特性,將其例項化成自定義元件。有意思的是,因為元件巢狀關係的存在,自定義元件之間就像DOM樹一樣也是一個倒長的樹形結構。
感謝讀到這裡的兄臺,有的兄臺可能會說,這篇文章大談特談了一堆元件、元件化開發,但都是理論性的東西。說好聽了叫方法論,說不好聽了是扯淡。若不來點實際東西,那便是虛與委蛇之氣溢於朱墨之表,撲人眉宇。那麼接下面的幾篇文章,我將與大家一起根據本文的理論,一步步實現一套基礎的元件類庫。
參考文章: