什么是前端組件化和模塊化?
這兩天一直在思考這個問題导匣,以前對這兩個概念的理解很模糊茸时。認為“模塊化”是側重于功能或者數(shù)據的封裝,目的是為了解耦合厕氨;而“組件化”更關注的UI部分命斧,如一個頁面可以分為頭部嘱兼、底部和內容區(qū)域等等。
這樣的理解很明顯是表層的簡單的可能還是不正確的理解汇四,最近反復閱讀了蘇寧前端“代碼民工徐飛”關于組件化話的幾篇文章踢涌,結合Angular1.x和Vue1.x的組件思想對組件化和模塊化有了一些新的認識(原文鏈接在文末)睁壁。
什么是組件化
組件化的概念在后端早已存在多年,只不過近幾年隨著前端的發(fā)展行剂,這個概念在前端開始被頻繁提及钳降,特別是在MV*的框架中遂填。
前端中的“組件化”這個詞,在UI這一層通常指“標簽化”备燃,也就是把大塊的業(yè)務界面凌唬,拆分成若干小塊漏麦,然后進行組裝撕贞。
狹義的組件化一般是指標簽化测垛,也就是以自定義標簽(自定義屬性)為核心的機制食侮。
廣義的組件化包括對數(shù)據邏輯層業(yè)務梳理,形成不同層級的能力封裝链快。
為什么要有組件化眉尸?
不管是前端組件化還是后端的組件化,我認為其目的無非就是為了提高開發(fā)效率和后期維護的效率霉祸。
在說的詳細點丝蹭,就是比如我想實現(xiàn)一個網站的頭部坪蚁,我可以把頭部單獨拿出來進行封裝,根據不同頁面或者說業(yè)務要求,可以靈活定制不同的頭部(結構一致茵典,顏色或展示等不同)宾舅。這樣就可以在不同的頁面進行靈活的復用筹我,后期如果頭部結構有大的變動,可以只修改該頭部組件就好了结澄。是不是這個概念很熟悉,其實你早就在這么干了们妥,比如以前用jsp做靜態(tài)頁面生成的時候勉吻,就可以利用jsp的<jsp:include page="xxx.jsp"/>
指令進行引入公共組件齿桃,可能也有人理解那是模板的概念。
MV*框架中的組件
大概了解了一下組件化的概念带污,我們來看看Vue和Angular中是怎樣運用組件的思想的踩娘。
Vue中的組件
組件(Component)是 Vue.js 最強大的功能之一养渴。組件可以擴展 HTML 元素,封裝可重用的代碼翘紊。在較高層面上藐唠,組件是自定義元素宇立, Vue.js 的編譯器為它添加特殊功能。在有些情況下柳琢,組件也可以是原生 HTML 元素的形式柬脸,以 is 特性擴展毙驯。
以上是Vue官網對于Vue中組件的解釋,其中在徐飛的從HTML Components的衰落看Web Components的危機一文中的評論部分垦巴,Vue的作者尤雨溪(github:yyx990803)發(fā)表了對民工叔叔(我只是小菜鳥,叫叔叔沒啥問題蛾号,哈哈)文章的看法鲜结,其中也提到了他對于組件的認識活逆。
Vue中關于組件的介紹是Vue中的重點部分蔗候,從它在Vue官方文檔中的篇幅就可以看出來。我認為組件中最重要的方面纫事,到目前為止我能理解的就兩部分:通訊和復用所灸。接下來重點介紹一下Vue對于通訊的實現(xiàn)爬立。
結合尤雨溪在民工叔叔文章中評論那樣,組件之間的通訊可分為從內向外和從外向內兩種抡秆。Vue對于這兩種通信時怎樣解決的呢儒士?文檔中說的很詳細了檩坚,events up和props down。
從外向內的“props down”
具體來說,就是當父組件向子組件傳遞信息的時候剩檀,采用的是在子組件的構造對象中顯示的設置props屬性進行數(shù)據的傳輸沪猴。
Vue.component('child', {
// 聲明 props
props: ['message'],
// 就像 data 一樣,prop 可以用在模板內
// 同樣也可以在 vm 實例中像 “this.message” 這樣使用
template: '<span>{{ message }}</span>'
})
然后向他傳入一個普通字符串值:
<child message="hello!"></child>
當然為了實現(xiàn)字面量語法或者動態(tài)語法壶辜,可以使用v-bind
在父組件上來綁定數(shù)據砸民,詳細語法請參照Vue文檔奋救,本文不做詳細介紹尝艘。
需要特別注意的是:
- Vue默認的是單項數(shù)據流,當父組件的屬性變化時秒际,將傳導給子組件狡汉,但是不會反過來轴猎。這是為了防止子組件無意修改了父組件的狀態(tài)——這會讓應用的數(shù)據流難以理解。
- 另外锐峭,每次父組件更新時可婶,子組件的所有 prop 都會更新為最新值矛渴。這意味著你不應該在子組件內部改變 prop 。如果你這么做了蚕涤,Vue 會在控制臺給出警告揖铜。
- 注意在 JavaScript 中對象和數(shù)組是引用類型达皿,指向同一個內存空間天吓,如果 prop 是一個對象或數(shù)組贿肩,在子組件內部改變它會影響父組件的狀態(tài)。
并且當props為應用類型的時候還可以為其添加驗證龄寞。
從內向外的“events up”
Vue中默認的雖然是單項數(shù)據流汰规,但還是可以實現(xiàn)子組件向父組件傳輸數(shù)據的,因為有些場景中物邑,我們不可避免的會使用到溜哮。而Vue中向上傳遞數(shù)據采用的和Angular中一樣的思想,通過自定義事件的方式實現(xiàn)拂封。大體需要以下兩步步:
- 使用 $emit(eventName)在子組件上觸發(fā)事件
- 使用 $on(eventName) 在父組件上(或者祖先組件)監(jiān)聽事件
<div id="counter-event-example">
<p>{{ total }}</p>
//父組件監(jiān)聽事件
<button-counter v-on:increment="incrementTotal"></button-counter>
<button-counter v-on:increment="incrementTotal"></button-counter>
</div>
Vue.component('button-counter', {
template: '<button v-on:click="increment">{{ counter }}</button>',
data: function () {
return {
counter: 0
}
},
methods: {
increment: function () {
this.counter += 1;
this.$emit('increment');//子組件觸發(fā)事件
}
},
})
new Vue({
el: '#counter-event-example',
data: {
total: 0
},
methods: {
incrementTotal: function () {
this.total += 1;
}
}
})
中央事件總線
有時候非父子關系的組件也需要通信。在簡單的場景下冒签,使用一個空的 Vue 實例作為中央事件總線:
var bus = new Vue();
// 觸發(fā)組件 A 中的事件
bus.$emit('id-selected', 1)
// 在組件 B 創(chuàng)建的鉤子中監(jiān)聽事件
bus.$on('id-selected', function (id) {
// ...
})
在更多復雜的情況下在抛,你應該考慮使用專門的 狀態(tài)管理模式--Vuex
另外Vue還可以使用slot分發(fā)內容,具體實現(xiàn)和更多實現(xiàn)請參考Vue文檔萧恕。
簡單描述了一下Vue的組件思想刚梭,主要目的是從民工叔叔的組件化文章中重新認識到了組件化的思想,結合Vue框架去理解票唆,可能更容易明白民工叔叔的見解朴读。
Angular中的組件
嚴格來說,Angular1.x中并沒有明確提及組件的概念走趋,只是我們可以使用app.directive()
來實現(xiàn)自定義指令衅金,我覺著這其實就是組件。
而Angular中實現(xiàn)組件之間進行通信的方式主要有四種方式:
基于$rootScope的全局變量和$scope作用域的繼承性
基于作用域的繼承性來實現(xiàn)組件通信僅限于作用域鏈上的通信簿煌,需要對Angular的controller之間的作用域關系特別熟悉氮唯,詳細可以參考民工叔叔的AngularJS實例教程(二)——作用域與事件。
利用事件$on()姨伟、$emit()惩琉、$broastcase()方式
先上一張圖:

上圖中清晰的展示了Angular中利用事件去上傳數(shù)據和廣播事件的關系圖,于是我們就可以利用事件做點事情了夺荒。
從作用域往上發(fā)送事件瞒渠,使用scope.$emit
$scope.$emit("someEvent", {});
從作用域往下發(fā)送事件,使用scope.$broadcast
$scope.$broadcast("someEvent", {});
這兩個方法的第二個參數(shù)是要隨事件帶出的數(shù)據技扼。
注意伍玖,這兩種方式傳播事件,事件的發(fā)送方自己也會收到一份剿吻。
利用服務實現(xiàn)
利用myApp.factory()生成一個需要共享數(shù)據的對象窍箍,然后在controller中注入,就可以是獲取到共享數(shù)據了,并進行修改了仔燕。
直接上代碼:
var myApp = angular.module("myApp", []);
myApp.factory('Data', function() {
return {
name: "Ting"
}
});
myApp.controller('FirstCtrl', function($scope, Data) {
$scope.data = Data;
$scope.setName = function() {
Data.name = "Jack";
}
});
myApp.controller('SecondCtrl', function($scope, Data) {
$scope.data = Data;
$scope.setName = function() {
Data.name = "Moby";
}
});
訂閱發(fā)布模式
民工叔叔在AngularJS實例教程(二)——作用域與事件一文的末尾提出了利用訂閱發(fā)布模式來通信,我覺著太經典了魔招,接收方在這里訂閱消息晰搀,發(fā)布方在這里發(fā)布消息。這個過程可以用這樣的圖形來表示:

代碼寫起來也很簡單办斑,把它做成一個公共模塊外恕,就可以被各種業(yè)務方調用了:
app.factory("EventBus", function() {
var eventMap = {};
var EventBus = {
on : function(eventType, handler) {
//multiple event listener
if (!eventMap[eventType]) {
eventMap[eventType] = [];
}
eventMap[eventType].push(handler);
},
off : function(eventType, handler) {
for (var i = 0; i < eventMap[eventType].length; i++) {
if (eventMap[eventType][i] === handler) {
eventMap[eventType].splice(i, 1);
break;
}
}
},
fire : function(event) {
var eventType = event.type;
if (eventMap && eventMap[eventType]) {
for (var i = 0; i < eventMap[eventType].length; i++) {
eventMap[eventType][i](event);
}
}
}
};
return EventBus;
});
事件訂閱代碼:
EventBus.on("someEvent", function(event) {
// 這里處理事件
var c = event.data.a + event.data.b;
});
事件發(fā)布代碼:
EventBus.fire({
type: "someEvent",
data: {
aaa: 1,
bbb: 2
}
});
注意,如果在復雜的應用中使用事件總線乡翅,需要慎重規(guī)劃事件名鳞疲,推薦使用業(yè)務路徑,比如:"portal.menu.selectedMenuChange"蠕蚜,以避免事件沖突尚洽。
非常經典的Angular組件之間通信的一種方式,我在解釋一下靶累,如果A組件中有值需要傳遞給B組件腺毫。那么在B組件Controller中通過EventBus.on()
訂閱事件:
EventBus.on("someEvent", function(event) {
// 這里是B組件中對A組件傳過來的值進行處理的回調函數(shù)
var c = event.data.a + event.data.b;
});
然后,在A組件Controller中將其中的值通過EventBus.fire()
發(fā)布一下:
EventBus.fire({
type: "someEvent",
data: {
aaa: 1,
bbb: 2
}
});
這樣B組件就可以拿到A組件中的數(shù)據值了挣柬。
組件化再思考
簡單了解了一下Vue和Angular中的組件思想潮酒,我們再來回想一下組件化的概念:
狹義的組件化一般是指標簽化,也就是以自定義標簽(自定義屬性)為核心的機制邪蛔。
廣義的組件化包括對數(shù)據邏輯層業(yè)務梳理急黎,形成不同層級的能力封裝。
很明顯侧到,不管是Vue還是Angular勃教,對組件的封裝都是為了對數(shù)據邏輯業(yè)務的梳理,使得不同組件各司其職床牧,當然這其中就包括了對HTML的組件化荣回,CSS的組件化和JS的組件化。
對于HTML的組件化可以理解為利用各種語義化標簽或者自定義標簽(Vue中的組件和Angular中的自定義指令等)對結構進行封裝戈咳。而對于CSS的組件化心软,我們現(xiàn)在可以利用SASS或者LESS來實現(xiàn),根據不同的功能對樣式進行不同的封裝著蛙,我覺著現(xiàn)在前端的一些UI框架就是對組件化的使用删铃,去哪兒網杜瑤的Yo框架就是一個很好的例子。
至于對于JS的組件化運用踏堡,我個人覺著就是模塊化猎唁。不管是CommonJS規(guī)范、AMD規(guī)范顷蟆、CMD規(guī)范還是ES2015的模塊機制目的都是對JS進行模塊化開發(fā)诫隅,使得不同功能的邏輯或者業(yè)務分離開腐魂,每個模塊專注自己的業(yè)務邏輯,這樣不僅是開發(fā)的時候工作目錄一目了然逐纬,后期維護的時候也能快速定位到各個業(yè)務邏輯模塊蛔屹。
小結
簡單的對民工叔叔的幾篇文章做了一下總結,其實還沒有理解到位豁生,每篇文章讀了僅僅3-4遍兔毒,每讀一遍都有不一樣的感受,理解也不一樣甸箱∮可能等過段時間再去讀又會顛覆我現(xiàn)在的認識,不管對于錯芍殖,先記錄一下現(xiàn)在的感受豪嗽,后期有了新的認識再來補充或者修改。
下面推薦民工叔叔的對于組件化和Angular的一些文章豌骏,我做了少許整理昵骤,大家可以直接按照目錄由淺至深的閱讀原文。