1. 程式人生 > >前端元件化開發

前端元件化開發

Component,中文稱為元件,或者構件。使用非常比較廣泛,它的核心意義在於複用,相對模組,對於依賴性有更高的要求。
Module, 中文為模組或模組。它的核心意義是分離職責,屬於程式碼級模組化的產出。它本身是提供服務的功能邏輯,是一組具有一定內聚性程式碼的組合,職責明確。
元件(Component)和模組(Module)又是一對容易混淆的名詞,也常常被用來相互替換。個人總結,從設計上來看,元件強調複用,模組強調職責(內聚、分離),或者說元件是達到可複用要求的模組(記得有次面試的時候,面試官都搞錯了這兩個概念!)。
前端Web應用中的元件,是指一些設計為通用性的,用來構建較大型應用程式的軟體,這些元件有多種表現形式。它可以是有UI(使用者介面)的,也可以是作為 “服務”的純邏輯程式碼。因為有視覺上的表現形式,UI元件更容易理解。UI元件簡單的例子包括按鈕、輸入框和文字域。不論是漢堡包狀的選單按鈕(無論你是否喜歡)、標籤頁、日曆、選項選單或者所見即所得的富文字編輯器則是一些更加高階的例子。提供服務型別的元件可能會讓人難以理解,這種型別的例子包括跨瀏覽器的AJAX支援,日誌記錄或者提供某種資料持久化的功能。
基於元件開發,最重要的就是元件可以用來構成其他元件,而富文字編輯器就是個很好的例子。它是由按鈕、下拉選單和一些視覺化元件等組成。另一個例子是HTML5上的video元素。它同樣包含按鈕,也同時含有一個能從視訊資料流渲染內容的元素。

為什麼要構建元件?

高內聚

我們將相關的一些功能組織在一起,把一切封裝起來,而在元件的例子中,就可能是相關的功能邏輯和靜態資源:JavaScript、HTML、CSS以及影象等。這就是我們所說的內聚。這種做法將讓元件更容易維護,並且這麼做之後,元件的可靠性也將提高。同時,它也能讓元件的功能明確,增大元件重用的可能性。

可重用

你看到的示例元件,尤其是Web Component,更關心可重用的問題。功能明確,實現清晰,API易於理解。自然就能促進元件複用。通過構建可重用元件,我們不僅保持了 DRY(不要重複造輪子)原則,還得到了相應的好處。
這裡要提醒: 不要過分嘗試構建可重用元件。你更應該關注應用程式上所需要的那些特定部分。如果之後相應需求出現,或者元件的確到了可重用的地步,就花一點額外時間讓元件重用。事實上,開發者都喜歡去創造可重用功能塊(庫、元件、模組、外掛等),做得太早將會讓你後來痛苦不堪。所以,吸取基於元件開發的其他好處,並且接受不是所有元件都能重用的事實。

可互換

一個功能明確好元件的API能讓人輕易地更改其內部的功能實現。要是程式內部的元件是鬆耦合的,那事實上可以用一個元件輕易地替換另一個元件,只要遵循相同的 API/介面/約定。

可組合

基於元件的架構讓元件組合成新元件更加容易。這樣的設計讓元件更加專注,也讓其他元件中構建和暴露的功能更好利用。不論是給程式新增功能,還是用來製作完整的程式,更加複雜的功能也能如法炮製。這就是這種方法的主要好處。

元件化開發

這裡寫圖片描述

如上圖,簡單解讀一下:

  • 頁面上的每個獨立的可視/可互動區域視為一個元件;
  • 每個元件對應一個工程目錄,元件所需的各種資源都在這個目錄下就近維護
  • 由於元件具有獨立性,因此元件與元件之間可以 自由組合;
  • 頁面只不過是元件的容器,負責組合元件形成功能完整的介面;
  • 當不需要某個元件,或者想要替換元件時,可以整個目錄刪除/替換。

其中第二項描述的就近維護原則,是我覺得最具工程價值的地方,它為前端開發提供了很好的分治策略,每個開發者都將清楚的知道,自己所開發維護的功能單元,其程式碼必然存在於對應的元件目錄中,在那個目錄下能找到有關這個功能單元的所有內部邏輯,樣式也好,JS也好,頁面結構也好,都在那裡。
基於這樣的工程理念,我們很容易將系統以獨立的元件為單元進行分工劃分:

這裡寫圖片描述

由於系統功能被分治到獨立的元件中,粒度比較精細,組織形式鬆散,開發者之間不會產生開發時序的依賴,大幅提升並行的開發效率,也更容易支援多個團隊共同維護一個大型站點的開發。

整個前端專案可以劃分為這麼幾種開發概念:
- JS模組:獨立的演算法和資料單元,瀏覽器環境檢測(detect),網路請求(ajax),應用配置(config),DOM操作(dom),工具函式(utils),以及元件裡的JS單元;
- CSS模組:獨立的功能性樣式單元,柵格系統(grid),字型圖示(icon-fonts),動畫樣式(animate),以及元件裡的CSS單元;
- UI元件:獨立的可視/可互動功能單元 ,頁頭(header),頁尾(footer),導航欄(nav),搜尋框(search);
- 頁面:前端這種GUI軟體的介面狀態,是UI元件的容器 首頁(index),列表頁(list),使用者管理(user);
- 應用:整個專案或整個站點被稱之為應用,由多個頁面組成。

基於這些理念,前端開發就成了這個樣子:

綜合上面的描述,對於一般中小規模的專案,大致可以規劃出這樣的原始碼目錄結構:

這裡寫圖片描述

前端元件的複用性

所謂元件化,核心意義莫過於提取真正有複用價值的東西。那怎樣的東西有複用價值呢?

公共樣式的複用性也是比較容易認可的,因此也會有bootstrap,foundation,semantic這些東西的流行,不過它們也不是純粹的樣式庫了,也帶有一些小的邏輯封裝。業務邏輯這一塊的複用是存在很多爭議的,一方面,很多人不認同業務邏輯也需要元件化,另一方面,這塊東西究竟怎樣去元件化,也很需要思考。除了這些之外,還有大量的業務介面,這塊東西很顯然複用價值很低,基本不存在複用性,但仍然有很多方案中把它們“元件化”了,使得它們成為了“不具有複用性的元件”。
元件化的本質目的並不一定是要為了可複用,而是提升可維護性。

對於一個有一定規模的Web應用來說,把所有東西都“元件化”,在管理上會有較大的便利性。我舉個例子,同樣是編寫程式碼,短程式碼明顯比長程式碼的可讀性更高,所以很多語言裡會建議“一個方法一般不要超過多少行,一個類最好不要超過多少行”之類。
這個時候我們再看HTML的部分,如果不考慮模板等技術的使用,某些介面光佈局程式碼寫起來就非常多了,像一些表單,都需要一層套一層,很多簡單的表單元素都需要套個三層左右,更不必說一些有複雜佈局的東西了。尤其是整個系統單頁化之後,介面的header,footer,各種nav或者aside,很可能都有一定複雜性。如果這些東西的程式碼不作切分,那麼主介面的HTML一定比較難看。
從這個角度看,這些拆出去的東西都像元件,但如果從複用性的角度看,很可能多數東西,每一塊都只有一個地方用,壓根沒有複用度。這個拆出去,純粹是為了使得整個工程易於管理,易於維護。

這時候我們再來關注不同框架/庫對UI層元件化的處理方式,發現有兩個型別,模板和函式。
模板是一種很常見的東西,它用HTML字串的方式表達介面的原始結構,然後通過代入資料的方式生成真正的介面,有的是生成目標HTML,有的還生成各種事件的自動繫結。前者是靜態模板,後者是動態模板。
另外有一些框架/庫偏愛用函式邏輯來生成介面,早期的ExtJS,現在的React(它內部還是可能使用模板,而且對外提供的是元件建立介面的進一步封裝——jsx)等,這種實現技術的優勢是不同平臺上程式設計體驗一致,甚至可以給每種平臺封裝相同的元件,呼叫方輕鬆寫一份程式碼,在Web和不同Native平臺上可用。但這種方式也有比較麻煩的地方,那就是介面調整比較繁瑣。

有這麼一個簡單場景:一個僱員列表介面包括兩個部分,僱員表格和用於填寫僱員資訊的表單。在這個場景下,存在哪些元件?

對於這個問題,主要存在兩種傾向,一種是僅僅把“控制元件”和比較有通用性的東西封裝成元件,另外一種是整個應用都元件化。對前一種方式來說,這裡面只存在資料表格這麼一個元件。 對後一種方式來說,這裡面有可能存在:資料表格,僱員表單,甚至還包括僱員列表介面這麼一個更大的元件。這兩種方式,就是我們之前所說的“區域性元件化”,“全元件化”。

我們前面提到,全元件化在管理上是存在優勢的,它可以把不同層面的東西都搞成類似結構,比如剛才的這個業務場景,很可能最後寫起來是這個樣子:

<Employee-Panel>
<Employee-List></Employee-List>
<Employee-Form></Employee-Form>
</Employee-Panel>

全標籤化的問題主要有這些:
第一,語義化代價太大。只要用了標籤,就一定需要給它合適的語義,也就是命名。但實際用的時候,很可能只是為了把一堆html簡化一下而已,到底簡化出來的那東西應當叫什麼名字,光是起名也費不知多少腦細胞。第二,配置過於複雜。有很多東西其實不太適合封裝,不但封裝的代價大,使用的代價也會很大。有時候會發現,呼叫程式碼的絕大部分都是在寫各種配置。

這個問題討論完了,我們來看看另外一個問題:如果UI元件有業務邏輯,應該如何處理。

比如說,性別選擇的下拉框,它是一個非常通用化的功能,照理說是很適合被當做元件來提供的。但是究竟如何封裝它,我們就有些犯難了。這個元件裡除了介面,還有資料,這些資料應當內建在元件裡嗎?理論上從元件的封裝性來說,是都應當在裡面的,於是就這麼造了一個元件:

<GenderSelect></GenderSelect>

性別的資料很自然地是放在元件的實現內部,一個寫死的陣列中。這個太簡單了,我們改一下,改成商品銷售的國家下拉框。但我們有個要求,本公司商品銷售的國家的資訊是統一配置的,也就是說,這個資料來源於服務端。這時候,你是不是想把一個http請求封裝到這元件裡?

這樣做也不是不可以,但存在至少兩個問題:

如果這類元件在同一個介面中出現多次,就可能存在請求的浪費,因為有一個元件例項就會產生一個請求。 如果國家資訊的配置介面與這個元件同時存在,當我們在配置介面中新增一個國家了,下拉框元件中的資料並不會實時重新整理。 第一個問題只是資源的浪費,第二個就是資料的不一致了。曾經在很多系統中,大家都是手動重新整理當前頁面來解決這問題的,但到了這個時代,人們都是追求體驗的,在一個全元件化的解決方案中,不應再出現此類問題。
如何解決這樣的問題呢?那就是引入一層Store的概念,每個元件不直接去到服務端請求資料,而是到對應的前端資料快取中去獲取資料,讓這個快取自己去跟服務端保持同步。所以,在實際做方案的過程中,不管是基於Angular,React,Polymer,最後肯定都做出一層Store了,不然會有很多問題。