上一篇博文梳理了vue的數據驅動和響應式相關的特性,這一篇博文就來梳理vue的一個很重要的特性,組件化。
自定義組件之於vue,其意義不亞於函數之於C,java之類的編程語言。
函數是計算機科學中的一大重要的發明。
一方面,它表明着一種自頂向下,逐步求精的分而治之的思惟,另一方面,它可以封裝複雜實現的細節,提供更高抽象的接口,下降軟件工程的複雜度。html
在vue中,自定義組件也起着相似的做用。vue
<!--more-->
咱們知道,在組件化的GUI界面上,GUI能夠被視爲一棵樹,瀏覽器的DOM就是一個最好的例子。
從佈局上來看,界面能夠當作大盒子套小盒子,小盒子再套更小的盒子。。。
反映到DOM上,DOM某節點的全部子節點,都是該組件的子組件,都是該組件內的元素。java
在vue中也是如此,vue組件之間的關係也是相似DOM同樣,是樹狀的。
在定義一個組件時,須要引用的全部組件,都成爲了該組件的子組件。git
組件做爲一個模塊性質的東西,天然就有着它必定的獨立性。並且,與其它模塊的耦合都理所應當的有着明確的接口約定。
在vue中,父子組件通訊經過組件屬性和事件來進行的。
其中,經過組件屬性,父組件的數據流向子組件;經過事件,子組件的數據流向父組件。github
從抽象的角度看,組件做爲一個黑盒子,它有着特定的屬性用以接收外部傳遞給它的數據,它也有着特定的事件,當特定操做發生時調用回調函數,以通知別的組件。編程
<div id="x-child"> <span> {{ titleMessage }} <span> </div> <script> Vue.component('x-child', { template: '#x-child', props: ['titleMessage'] }); </script>
<div id="x-parent"> <x-child title-message="A"></x-child> <x-child title-message="B"></x-child> <x-child :title-message="message"></x-child> </div> <script> Vue.component('x-parent', { template: '#x-parent', data: function () { return { message: "C" }; } }); </script>
上面的例子中,定義了x-child自定義組件,而且在x-parent組件中引用它。
以前在介紹數據驅動的時候,咱們知道,定義vue組件時,能夠經過data定義組件內部的狀態,它是組件數據的一部分。
除了data以外,prop(屬性)也是組件數據的來源之一,父組件經過prop將本身的數據傳遞給子組件。segmentfault
在定義組件時咱們能夠看到:數組
在使用自定義組件時能夠看到:瀏覽器
vue中,屬性被設計用於父組件傳遞數據給子組件的,若是子組件改變了屬性,那麼父組件不會受到任何影響。這在vue中被稱爲 單向數據流。
可是,若是屬性用來傳遞數組或對象等複合的數據結構,那麼可能會出問題。考慮如下的場景:數據結構
問題在於,因爲js是引用類型語言,簡單的賦值僅僅是傳遞引用,那麼,以上場景中,父組件中的數據,子組件中的屬性,還有子組件中的狀態, 指向的都是同一份對象 !
這會形成一個問題,若是子組件修改了該對象的屬性,那麼父組件的數據也會受到影響,這破壞了單向數據流,會形成不少詭異的bug。
解決方法也很顯然:
<div id="x-child"> <button @click="onClick">click</button> </div> <script> Vue.component('x-child', { template: '#x-child', data: function() { return { counter: 0 }; }, methods: { onClick: function() { this.counter++; this.$emit("on-counter-add", this.counter); } } }); </script>
以上自定義了x-child組件,而且自定義了組件事件。咱們能夠看到:
<div id="x-parent"> <x-child @on-counter-add="onCounterAdd"></x-child> <span> { { counter }} </span> </div> <script> Vue.component('x-parent', { template: '#x-parent', data: function () { return { counter: 0 }; }, methods: { onCounterAdd: function (counter) { this.counter = counter; } } }); </script>
以上定義了x-parent組件,而且引用了上面定義的子組件。能夠看出:
在監聽事件的地方,上面的寫法是使用了一個回調函數,不過,也可使用js表達式,好比:
<x-child @on-counter-add="counter = arguments[0]"></x-child>
上面代碼的重點在於arguments[0]
,若是是js表達式寫法,使用arguments引用事件的參數,就好像這段js表達式被放入了一個vue提供的匿名函數,而後使用匿名函數監聽這個事件同樣。
那它有什麼用呢?在上面的場景裏這樣寫固然是很差的,由於削弱了可讀性。
以前在我同事碰到的一個場景裏,是一個涉及到插槽分發做用域的場景,若是寫成回調函數的形式,那麼在回調函數中沒法訪問插槽做用域的變量。
所以,必須使用js表達式的寫法,將插槽做用域中的變量顯式的帶到回調函數中,代碼相似這種,懶得構造具體的例子了 :
<x-child @on-counter-add="onCountAdd(arguments[0], scope.id)"></x-child>
因爲vue設計的父子組件通訊是單向數據流,可是因爲一些需求的須要,若是能提供雙向數據流,會使使用起來更方便。
便捷性和設計的統一性衝突,怎麼辦?固然是用語法糖解決了。
實際上,vue提供的兩種好像是雙向數據流的機制,.sync
和 v-model
,都是語法糖。
<comp :foo.sync="bar"></comp>
這種寫法只是下面的語法糖:
<comp :foo="bar" @update:foo="val => bar = val"></comp>
子組件內,若是修改了foo時,須要觸發update:foo
事件。
v-model經常使用於相似表單這樣的自定義控件:
<my-checkbox v-model="foo"></my-checkbox>
它也是以下語法的語法糖:
<my-checkbox :value="foo" @input="val => foo = val" > </my-checkbox>
仔細思考剛纔的自定義組件的定義,不難發現,上面的自定義組件只能對DOM中的一棵子樹作抽象和封裝。
那麼,考慮這樣一種狀況,咱們封裝了一個card組件,card的內容可使用任意的vue組件填充。
這種場景,就須要在自定義組件時,可以在組件的DOM樹裏 挖個洞 ,這個洞可以讓該組件的調用者填充。
vue提供的這種相似的機制,被稱爲插槽。
<div id="x-my-card"> <h2>我是子組件的標題 <slot name="title"></slot> </h2> <slot> </slot> </div> <script> Vue.component('x-my-card', { template: '#x-my-card' }); </script>
<div id="x-component"> <x-my-card> <p>這是一些初始內容</p> <p>這是更多的初始內容</p> </x-my-card> <x-my-card> <h2 slot="title">標題</h2> <p>這是一些初始內容</p> <p>這是更多的初始內容</p> </x-my-card> </div> <script> Vue.component('x-component', { template: '#x-component' }); </script>
從上面的示例中能夠看到:
slot
標籤給自定義組件留了一個「洞」。slot
標籤的name
屬性定義插槽名稱以區分不一樣的插槽,這樣可以在自定義組件上挖多個」洞」。vue提供的插槽機制,在給自定義組件挖」洞」的同時,還能使自定義組件給洞裏填充的組件傳遞數據。以下:
<div id="x-my-card"> <slot text="hello from child"></slot> </div>
<div id="x-component"> <x-my-card> <template slot-scope="scope"> <span>{{ scope.text }}</span> </template> </x-my-card> </div>
從上面能夠看出:
slot
時,能夠經過屬性將數據傳遞給它。在引用自定義組件的地方,將插槽內容放入template
標籤內,經過slot-scope
指定變量名,便可在template
標籤內引用該變量從而使用插槽傳遞過來的數據。本篇博文梳理了vue的自定義組件機制,經過自定義組件,就可以在vue項目中很好的將項目組件化。
一方面,可以提取共同的組件進行復用,下降代碼冗餘;另一方面,也可以提供一種強大的抽象機制,提升vue的表達能力。
注:該文於2018-04-10撰寫於個人github靜態頁博客,現同步到個人segmentfault來。