組件(Component)是Vue.js最核心的功能,也是整個框架設計最精彩的地方,當然也是最難掌握的。本章將帶領你由淺入深地學習組件的全部內容,並通過幾個實戰項目熟練使用Vue組件。 ...
本篇目錄:
組件(Component)是Vue.js最核心的功能,也是整個框架設計最精彩的地方,當然也是最難掌握的。
本章將帶領你由淺入深地學習組件的全部內容,並通過幾個實戰項目熟練使用Vue組件。
7.1 組件與復用
7.1.1 為什麼使用組件
在正式介紹組件前,我們先來看一個簡單的場景,如圖7-1所示:

圖7-1中是一個很常見的聊天界面,有一些標準的控制項,比如右上角的關閉按鈕、輸入框、發送按鈕等。
你可能要問了,這有什麼難的,不就是幾個<div>
、<input>
嗎?
好,那現在需求升級了,這幾個控制項還有別的地方要用到。
沒問題,複製粘貼唄。
那如果輸入框要帶數據驗證,按鈕的圖標支持自定義呢?
這樣用JavaScript封裝後一起複制吧。
那等到項目快完結時,產品經理說,所有使用輸入框的地方,都要改成支持回車鍵提交。
好吧,給我一天的事件,我一個一個加上去。
上面的需求雖然有點變態,但卻是業務中很常見的,那就是一些控制項、JavaScript能力的復用。
沒錯,Vue.js的組件就是提高重用性的,讓代碼可重用。
當學習完組件後,上面的問題就可以分分鐘搞定了,再也不用害怕鏟平經理的奇葩需求。
我們先看一下圖7-1中的示例用組件來編寫是怎麼的,示例代碼如下:
1 <Card style="width:350px;"> 2 <p slot="title">與 xxx 聊天中</p> 3 <a href="#" slot="extra"> 4 <Icon type="android-close" size="18"></Icon> 5 </a> 6 <div style="height:100px;"></div> 7 <div> 8 <Row :gutter="16"> 9 <i-col span="17"> 10 <i-input v-model="value" placeholder="請輸入..."></i-input> 11 </i-col> 12 <i-col span="4"> 13 <i-button v-model="primary" icon="paper-airplane">發送</i-button> 14 </i-col> 15 </Row> 16 </div> 17 </Card>
是不是很奇怪,有很多我們從來都沒有見過的標簽,比如<Card>
、<Row>
、<i-col>
、<input>
和<i-button>
等。
而且整段代碼除了內聯的幾個樣式外,一句CSS代碼也沒有,但最終實現的UI就是圖7-1的效果。
這些沒見過的自定義標簽就是組件,每個標簽代表一個組件,在任何使用Vue的地方都可以直接使用。
接下來,我們就看看組件的具體用法。
7.1.2 組件用法
回顧一下我們創建Vue實例的方法:
1 var app = new Vue({ 2 el: "#app" 3 });
組件與之類似,需要註冊之後才可以使用。註冊有全局註冊和局部註冊兩種方式。
全局註冊後,任何Vue實例都可以使用。全局註冊示例代碼如下:
1 Vue.component("my-component", { 2 // 選項 3 });
my-component
就是註冊的組件自定義標簽名稱,推薦使用小寫加減號分割的形式命名。
要在父實例中使用這個組件,必須要在實例創建前註冊。
之後就可以用<my-component></my-component>
的形式來使用組件了。
實例代碼如下:
1 <div id="app"> 2 <my-component></my-component> 3 </div> 4 5 <script> 6 Vue.component("my-component", { 7 // 選項 8 }); 9 10 var app = new Vue({ 11 el: "#app" 12 }); 13 </script>
此時打開頁面還是空白的,因為我們註冊的組件沒有任何內容。
在組件選項中添加template
就可以顯示組件內容了。
實例代碼如下:
1 Vue.component("my-component", { 2 template: "<div>這裡是組件的內容</div>" 3 });
渲染後的結果是:
1 <div id="app"> 2 <div>這裡是組件的內容</div> 3 </div>
template
的DOM結構必須被一個元素包含,如果直接寫成“這裡是組件的內容”,不帶<div></div>
是無法渲染的。
在Vue實例中,使用components
選項可以局部註冊組件,註冊後的組件只有在該實例作用域下有效。
組件也可以使用components
選項來註冊組件,該組件可以嵌套。
示例代碼如下:
1 <div id="app"> 2 <my-component></my-component> 3 </div> 4 5 <script> 6 var Child = { 7 template: "<div>局部註冊組件的內容</div>" 8 }; 9 10 var app = new Vue({ 11 el: "#app", 12 components: { 13 "my-component": Child 14 } 15 }); 16 </script>
Vue組件的模板在某些情況下回收到HTML的限制,比如<table>
內規定只允許是<tr>
、<td>
、<th>
等這些表格元素,所以在<table>
內直接使用組件是無效的。
這種情況下,可以使用特殊的is
屬性來掛載組件,示例代碼如下:
1 <div id="app"> 2 <table> 3 <tbody is="my-component"></tbody> 4 </table> 5 </div> 6 7 <script> 8 Vue.component("my-component", { 9 template: "<div>這裡是組件的內容</div>" 10 }); 11 12 var app = new Vue({ 13 el: "#app" 14 }); 15 </script>
<tbody>
在渲染時,會被替換為組件的內容。
常見的限制元素還有<ul>
、<ol>
、<select>
。
提示:
如果使用的是字元串模板,是不受限制的,比如後面章節介紹的.vue
單文件用法等。
除了template
選項外,組件還可以像Vue實例那樣使用其他的選項,比如data
、computed
、method
等。
但是在使用data
時,和實例稍有區別,data
必須是函數,然後將數據return
出去。
例如:
1 <div id="app"> 2 <my-component></my-component> 3 </div> 4 5 <script> 6 Vue.component("my-component", { 7 template: "<div>{{message}}</div>", 8 data: function() { 9 return { 10 message: "組件內容" 11 }; 12 } 13 }); 14 15 var app = new Vue({ 16 el: "#app" 17 }); 18 </script>
JavaScript對象是引用關係,所以如果return
出的對象引用了外部的一個對象,那這個對象就是共用的,任何一方修改都會同步。
比如下麵的示例:
1 <div id="app"> 2 <my-component></my-component> 3 <my-component></my-component> 4 <my-component></my-component> 5 </div> 6 7 <script> 8 var data = { 9 counter: 0 10 }; 11 12 Vue.component("my-component", { 13 template: "<button @click='counter++'>{{counter}}</button>", 14 data: function() { 15 return data; 16 } 17 }); 18 19 var app = new Vue({ 20 el: "#app" 21 }); 22 </script>
組件使用了3次,但是點擊任意一個<button>
,3個按鈕的數字都會加1。
那是因為組件的data
引用的是外部的對象,這肯定不是我們期望的效果。
所以給組件返回一個新的data
對象來獨立,示例代碼如下:
1 <div id="app"> 2 <my-component></my-component> 3 <my-component></my-component> 4 <my-component></my-component> 5 </div> 6 7 <script> 8 Vue.component("my-component", { 9 template: "<button @click='counter++'>{{counter}}</button>", 10 data: function() { 11 return { 12 counter: 0 13 }; 14 } 15 }); 16 17 var app = new Vue({ 18 el: "#app" 19 }); 20 </script>
這樣,點擊3個按鈕就互不影響了,完全達到復用的目的。
7.2 使用props傳遞數據
7.2.1 基本用法
組件不僅僅是要把模板的內容進行復用,更重要的是組件間要進行通信。
通常父組件的模板中包含子組件,父組件要正向地向子組件傳遞數據或參數,子組件接收到後根據參數的不同來渲染不同的內容或執行操作。
這個正向傳遞數據的過程就是通過props
來實現的。
在組件中,使用選項props
來聲明需要從父級接收的數據。
props
的值可以是兩種,一種是字元串數組,一種是對象,本小節先介紹數組的用法。
比如我們構造一個數組,接收一個來自父級的數據message
,並把它在組件模板中渲染,示例代碼如下:
1 <div id="app"> 2 <my-component message="來自父組件的數據"></my-component> 3 </div> 4 5 <script> 6 Vue.component("my-component", { 7 props: ["message"], 8 template: "<div>{{message}}</div>" 9 }); 10 11 var app = new Vue({ 12 el: "#app" 13 }); 14 </script>
渲染後的結果為:
1 <div id="app"> 2 <div>來自父組件的數據</div> 3 </div>
props
中聲明的數據與組件data
函數return
的數據主要區別就是props
的來自父級,而data
中的是組件自己的數據,作用域是組件本身,這兩種數據都可以在模板template
及計算屬性computed
和方法methods
中使用。
上例的數據message
就是通過props
從父級傳遞過來的,在組件的自定義標簽上直接寫該props
的名稱,如果要傳遞多個數據,在props
數組中添加項即可。
由於HTML特性不區分大小寫,當使用DOM模板時,駝峰命名(camelCase)的props
名稱要轉為短橫分隔命名(kebab-case)。例如:
1 <div id="app"> 2 <my-component warning-text="提示信息"></my-component> 3 </div> 4 5 <script> 6 Vue.component("my-component", { 7 props: ["warningText"], 8 template: "<div>{{warningText}}</div>" 9 }); 10 11 var app = new Vue({ 12 el: "#app" 13 }); 14 </script>
提示:
如果使用的是字元串模板,仍然可以忽略這些限制。
有時候,傳遞的數據並不是直接寫死的,而是來自父級的動態數據,這是可以使用指令v-bind
來動態綁定props
的值,當父組件的數據變化時,也會傳遞給子組件。示例代碼如下:
1 <div id="app"> 2 <input type="text" v-model="parentMessage"> 3 <my-component :message="parentMessage"></my-component> 4 </div> 5 6 <script> 7 Vue.component("my-component", { 8 props: ["message"], 9 template: "<div>{{message}}</div>" 10 }); 11 12 var app = new Vue({ 13 el: "#app", 14 data: { 15 parentMessage: "" 16 } 17 }); 18 </script>
這裡用v-model
綁定了父級的數據parentMessage
。
當通過輸入框任意輸入時,子組件接收的props
(message
)也會實時響應,並更新組件模板。
提示:
註意,如果你要直接傳遞數字、布爾值、數組、對象,而且不使用v-bind
,傳遞的僅僅是字元串,嘗試下麵的示例來對比。1 <div id="app"> 2 <my-component message="[1,2,3]"></my-component> 3 <my-component :message="[1,2,3]"></my-component> 4 </div> 5 <script> 6 Vue.component("my-component", { 7 props: ["message"], 8 template: "<div>{{message.length}}</div>" 9 }); 10 var app = new Vue({ 11 el: "#app" 12 }); 13 </script>同一個組件使用了兩次,區別僅僅是第二個使用的是
v-bind
。渲染後的結果:第一個是7,第二個才是數組的長度3。
7.2.2 單向數據流
Vue 2.x與Vue 1.x比較大的一個改變就是,Vue 2.x通過props
傳遞數據是單向的了,也就是父組件數據變化時會傳遞給子組件,但是反過來不行。
而在Vue 1.x里提供了.sync
修飾符來支持雙向綁定。
之所以這樣設計,是儘可能將父子組件解耦,避免子組件無意中修改了父組件的狀態。
業務中經常用到兩種需要改變prop
的情況,一種是父組件傳遞初始值進來,子組件將它作為初始值保存起來,在自己的作用域下可以隨意使用和修改。
何種情況可以在組件data
內再聲明一個數據,引用父組件的prop
,實例代碼如下:
1 <div id="app"> 2 <my-component :init-count="1"></my-component> 3 </div> 4 5 <script> 6 Vue.component("my-component", { 7 props: ["initCount"], 8 template: "<div>{{count}}</div>", 9 data: function() { 10 return { 11 count: this.initCount 12 }; 13 } 14 }); 15 16 var app = new Vue({ 17 el: "#app" 18 }); 19 </script>
組件中聲明瞭數據count
,它在組件初始化時會獲取來自父組件的initCount
,之後就與之無關了,只用維護count
,這樣就可以避免直接操作initCount
。
另一種情況就是prop
作為需要轉變的原始值傳入。
這種情況用計算屬性就可以了,示例代碼如下:
1 <div id="app"> 2 <my-component :width="100"></my-component> 3 </div> 4 5 <script> 6 Vue.component("my-component", { 7 props: ["width"], 8 template: "<div :style='style'>組件內容</div>", 9 computed: { 10 style: function() { 11 return { 12 width: this.width + "px" 13 }; 14 } 15 } 16 }); 17 18 var app = new Vue({ 19 el: "#app" 20 }); 21 </script>
因為用CSS傳遞寬度要帶單位(px),但是每次都寫太麻煩,而且數值計算一般是不帶單位的,所以統一在組件內使用計算屬性就可以了。
提示:
註意,在JavaScript中對象和數組是引用類型,指向同一個記憶體空間,所以props
是對象和數組時,在子組件內改變時會影響父組件的。
7.2.3 數據驗證
我們上面所介紹的props
選項的值都是一個數組,一開始也介紹過,除了數組外,還可以是對象,當prop
需要驗證時,就需要對象寫法。
一般當你的組件需要提供給別人使用時,推薦都進行數據驗證。
比如某個數據必須是數字類型,如果傳入字元串,就會在控制台彈出警告。
一下是幾個prop
的示例:
1 Vue.component("my-component", { 2 props: { 3 // 必須是數字類型 4 propA: Number, 5 // 必須是字元串或數字類型 6 propB: [String, Number], 7 // 布爾值,如果沒有定義,預設值就是true 8 propC: { 9 type: Boolean, 10 default: