Vue的組件是可復用的 Vue 實例,且帶有一個名字 。我們可以在一個通過 new Vue
創建的 Vue 根實例中,把這個組件作為自定義元素來使用。因為組件是可復用的 Vue 實例,所以它們與 new Vue
接收相同的選項,例如 data
、computed
、watch
、methods
以及生命周期鈎子等。僅有的例外是像 el
這樣根實例特有的選項。
一 創建組件
Vue提供了三種不同的方式來定義組件,分別是:全局組件,私有組件,單文件組件。接下來就讓我一一道來。
1,全局組件
注冊全局組件非常簡單,也是很常用的一種方式。
1 Vue.component('myCom',{ 2 template:'<div><p>我是一個全局<span>組件</span></p></div>'
3 });
Vue.component()方法需要兩個參數:
第一個,組件名稱;
第二個,實例(初始化)對象,可以包含所有使用new方式創建Vue實例時提供的所有屬性,除了el。
注意:組件的實例對象必須提供一個template屬性,用作該組件的HTML代碼模板,且在該模板中有且只能有一個根元素。全局組件的注冊必須在創建Vue實例之前。
小技巧:由於在編寫JS時,一般沒有HTML代碼提示,創建組件模板代碼會很不方便,所有可以在HTML文件中使用<template>元素創建模板,然后在組件的template屬性中使用id選擇器引用該模板。
注意:<template>元素必須在new Vue實例接管的根元素外部。
1 <template id="tem">
2 <div>
3 <p>我是組件內的p</p>
4 <span>我是組件中的span</span>
5 </div>
6 </template>
7 <!-- 在HTML中 -->
1 Vue.component('myCom',{ 2 template:'#tem' 3 });
4 //在組件中
2,私有組件
全局創建的組件在所有Vue實例中均可以使用,有時候這並不符合我們的需求。你可以通過以下方式定義Vue實例的私有組件,這些組件只能在該Vue實例根元素內部使用。
1 var vm = new Vue({ 2 el:'#app', 3 components:{ 4 mycom:{ 5 template:'#tem'
6 } 7 } 8 });
通過Vue實例的components屬性可以定義私有組件,該屬性綁定一個對象,對象的屬性名是組件名,屬性值是組件實例對象。
3,單文件組件
Vue的單文件組件是一個以.vue為后綴名的文件。由於HTML和JavaScrip不能識別.vue文件,所以不能直接使用這種方式的組件,必須配合webpack或vue-cli工具才能正確解析.vue文件。這里的重點是Vue單文件組件,所以有興趣的同學請移步webpack中文網。
1 <tempalte>
2 //HTML模板
3 </template>
4 <script>
5 //JS代碼
6 </script>
7 <style>
8 //CSS代碼
9 </style>
.vue文件的名稱就是組件的名稱,其結構非常簡單、清晰:
<template>標簽是組件的HTML模板;
<script>標簽是邏輯代碼;
<style>標簽中是樣式代碼。
二 組件的使用
不管以哪種方式創建Vue組件,我們最終的目的是在HTML頁面中展示出來。本節將詳細介紹Vue組件使用方式。
1,組件標簽
要把我們創建的Vue組件添加到頁面中去,只需要把組件名當做標簽來使用即可。
1 Vue.component('myCom',{ 2 template:"#tem"
3 }); 4 var vm = new Vue({ 5 el:"#app"
6 }); 7 //JS部分
1 <div id="app">
2 <my-com></my-com>
3 </div>
4 <!-- HTML部分 -->
小技巧:注冊組件時,建議使用全小寫形式命名,因為HTML標簽要求使用小寫字母。如果你一定要遵守小駝峰命名規則,那么你應該在使用組件時用“-”短橫線把單詞分隔開。
2,組件復用
Vue的組件可以重復使用。
1 <div id="app">
2 <my-com></my-com>
3 <my-com></my-com>
4 <my-com></my-com>
5 </div>
當然,全局組件可以在任何地方使用,而私有組件只能在實例接管元素內部使用。
組件不僅可以簡單的重復使用,還可以嵌套。
1 var vm = new Vue({ 2 el:'#app', 3 compontents:{ 4 mycom1:{ 5 template:'<div>組件一 <mycom2></mycom2></div>'
6 }, 7 mycom2:{ 8 template:'<div>組件二</div>'
9 } 10 } 11 });
3,另一種使用方式
1 var mycom = { 2 tempalte:'<div id="app2">hello</div>'
3 }; 4 var vm = new Vue({ 5 el:'#app', 6 render:function(createEl){ 7 return createEl(mycom); 8 } 9 });
使用render方式渲染組件:給Vue實例添加render屬性,該屬性值是一個接收一個方法作為參數的函數。參數用於創建Vue組件,return該組件將替代Vue實例接管的#app元素。最終的表現是:頁面上將不再出現#app的div,取而代之的是#app2的div。
這種方式一般配合單文件組件使用,如果要渲染多個組件,只需要創建多個Vue實例即可。
三 數據傳遞(通信)
1,props傳遞數據(父組件 --> 子組件)
通過在子組件綁定props屬性,實現父組件向子組件傳遞數據。props屬性值是一個數組,數組元素被定義用來接收父組件傳遞來的數據,然后通過v-bind指令指定數組元素接收哪些數據。子組件通過訪問props數組元素就可以訪問到父組件傳遞過來的數據了,就如同訪問data里面的值。
1 <div id="app">
2 <mycom :fromFatherMsg="toSonMsg"></mycom>
3 </div>
4 <!-- HTML部分 -->
1 Vue.component({ 2 template:"<div>{{fromFatherMsg}}</div>", 3 props:["fromFatherMsg"] 4 }); 5 var vm = new Vue({ 6 el:'#app', 7 data:{ 8 toSonMsg:'這是給子組件的數據'
9 } 10 }); 11 //JS部分
通過上面的例子,我們可以將這個過程簡單的分為三步:
第一步,在子組件上添加一個數組屬性props,在數組中定義用來接收數據的變量(以字符串形式存儲);
第二步,使用子組件時通過v-bind指令,綁定預先定義的接收變量和父組件將要傳遞過來的值;
第三步,在子組件中,如同訪問data中的數據一樣,訪問props數組元素接收到的數據。
2,$emit傳遞方法(父組件 --> 子組件)
父組件向子組件傳遞方法,是通過自定義事件來實現的。
監聽(同時注冊)自定義事件有兩種方式:組件上使用v-on指令、實例的$on()方法。這里我們將使用第一種方式來做演示。
子組件通過實例的$emit()方法觸發自定義事件,這里的事件名將成為$emit()方法的第一個參數。
1 <div id="app">
2 <mycom @fromFatherFun="toSonFun"></mycom>
3 </div>
4 <!-- HTML部分 -->
1 Vue.component({ 2 template:"<div><button @click="myFun">點擊執行來自父組件的方法</button></div>", 3 methods:{ 4 myFun:function(){ 5 this.$emit('fromFatherFun'); 6 } 7 } 8 }); 9 var vm = new Vue({ 10 el:'#app', 11 methods:{ 12 toSonFun(){ 13 console.log( "這是給子組件的方法"); 14 } 15 }); 16 //JS部分
注意:和傳遞數據一樣,子組件不能直接使用父組件的方法。子組件需要通過實例的$emit()方法間接執行來自父組件的方法。
這一過程也可以分為三步:
第一步,使用子組件時,通過v-on指令自定義一個事件;
第二步,綁定自定義事件和父組件需要傳遞的方法(指定回調函數);
第三步,通過子組件的$emit()方法(通過實參指定需要觸發的自定義事件)觸發父組件的方法執行;
3,子組件拋出值(子組件 --> 父組件)
子組件在通過$emit()觸發自定義事件時,可以同時利用方法的第二個參數,向外拋出一個值。回調函數(上面父組件傳遞下來的方法)需要定義一個形參來接收這個子組件拋出的值。
1 //接上面的例子
2 Vue.component({ 3 template:"<div><button @click="myFun">點擊執行來自父組件的方法</button></div>", 4 data(){ 5 return {name:'ren'}; 6 }, 7 methods:{ 8 myFun:function(){ 9 this.$emit('fromFatherFun',this.name); 10 } 11 } 12 }); 13 var vm = new Vue({ 14 el:'#app', 15 data:{ 16 nameFromSon:null
17 } 18 methods:{ 19 toSonFun(data){ 20 this.nameFromSon = data; 21 }, 22 });
子組件拋出一個值的原理和父組件給子組件傳遞方法原理是一樣的,只不過是不同的用法而已。雖然有點繞,但有用哦。
4,獲取子組件的引用
在使用子組件時,通過綁定ref屬性,父組件可以通過Vue實例的$refs屬性拿到子組件的引用,然后就可以直接訪問子組件的屬性或方法了。
1 <div id="app">
2 <mycom ref="sonCom"></mycom>
3 <button @click="printMsgFromSon">點擊打印子組件的信息</button>
4 </div>
5 <!-- HTML部分 -->
1 Vue.component('mycom',{ 2 data:function(){return {name:'ren'}} 3 }); 4
5 var vm = new Vue({ 6 el:'#app', 7 methods:{ 8 printMsgFromSon:function(){ 9 console.log(this.$refs.sonCom.name); 10 } 11 } 12 }); 13 //JS部分
小技巧:ref屬性不僅可以用在組件上,也可以用在其他標准HTML標簽上,這樣Vue實例就可以獲取到原生的DOM對象了。
注意:即使子組件是Vue實例的私有組件,實例也不能直接使用組件的相關數據,還是需要通過$refs等屬性來間接訪問。
5,兄弟組件間傳值
兄弟組件間實現通信也是采用自定義事件的形式。不過,這一般需要一個空的Vue實例作為中介,配合使用$on()和$emit()實現兄弟組件間的傳值。
1 <div id="app"> 2 <com1></com1> 3 <com2></com2> 4 </div> 5 <template id="com1"> 6 <div> 7 <p>com1組件:{{name}}</p> 8 <button @click="send">將數據發送給com2</button> 9 </div> 10 </template> 11 <template id="com2"> 12 <div> 13 <p>com2組件:{{name}}</p> 14 </div> 15 </template> 16 <!-- HTML部分 -->
1 var event = new Vue(); 2 var vm = new Vue({ 3 el:'#app', 4 components:{ 5 com1:{ 6 template:'#com1', 7 data(){ 8 return{name:'ren'}; 9 }, 10 methods:{ 11 send(){ 12 event.$emit('sendMsg',this.name); 13 } 14 } 15 }, 16 com2:{ 17 template:'#com2', 18 data(){ 19 return {name:null}; 20 }, 21 created(){//因為不知道什么時候會被觸發,所以一般選擇在生命周期鈎子中監聽 22 event.$on('sendMsg',name => {this.name = name;}) 23 //這里需要使用箭頭函數,使其內部的this指向com2 24 } 25 } 26 } 27 });
28 //JS部分
四 其他事項
1,單獨的data
經過上面的學習,你可能已經發現了一個問題:組件中的data屬性是一個函數返回的對象。
1 Vue.component("mycom",{ 2 template:"", 3 data(){ 4 return { //some code };
5 } 6 }); 7 //這是ES6的寫法,等同於data:function(){return {some code};}
由於data屬性綁定的是一個對象,而對象是一個引用類型,為了保證為每個組件維護一份獨立的數據,組件的data屬性必須是一個函數。
2,插槽<slot>
當你讀到這里時,你可能會有一個疑問:既然我們可以用標簽形式使用Vue組件,那么是否可以在開始標簽和結束標簽之間填些內容呢?如果可以的話,該如何做呢?Vue的答案是肯定的。
首先請看下面的例子:
1 <div id="app">
2 <com>我是插槽內容</com>
3 </div>
4 <!-- HTML部分 -->
1 Vue.compenent('com',{ 2 template:'<div><p>我是組件</p><slot>我是默認值<slot></div>'
3 }); 4 var vm = new Vue({ 5 el:'#app'
6 }); 7 //JS部分
"我是插槽內容"將替換com組件中<slot>元素。
注意:如果在使用子組件時沒有提供插槽值,那么<slot>元素中的默認值將會生效,前提是你已經定義了這些值。
上面的例子中,組件最終渲染的HTML結構如下:
1 <div>
2 <p>我是組件</p>
3 我是插槽內容 4 </div>
注意:插槽的內容不僅可以是文本內容,還可以是HTML代碼,甚至另一個組件。
如果你需要在一個組件中定義多個插槽,那么你應該需要用到<slot>元素的name屬性,來指定每個插槽應該擁有怎么樣的模板。
1 <div>
2 <com>
3 <template v-slot:"header">
4 <!-- 單獨的HTML模板 -->
5 </template>
6 <div><p>我是默認的模板</p></div>
7 <template v-slot:"footer">
8 <!-- 單獨的HTML模板 -->
9 </template>
10 </com>
11 </div>
12 <!-- HTML部分 -->
1 Vue.component('com',{ 2 tempalte:'<div><slot name="header"></slot><slot></slot><slot name="footer"></slot></div>'
3 }); 4 var vm = new Vue({ 5 el:'#app'
6 });
具名的插槽需要在使用組件時,用<template>元素單獨定義模板,並通過v-slot指令以參數的形式指定:“我是xxx插槽的模板”。
其他所有沒有包裹在<template>元素內的模板,將自動歸為匿名的<slot>元素下面。
3,特殊的嵌套元素
有些 HTML 元素,諸如 <ul>
、<ol>
、<table>
和 <select>
,對於哪些元素可以出現在其內部是有嚴格限制的。而有些元素,諸如 <li>
、<tr>
和 <option>
,只能出現在其它某些特定的元素內部。要怎樣才能在這些元素中正確的渲染組件呢?幸好,Vue提供了is特性:
1 <table>
2 <tr is="mycom"></tr>
3 </table>
注意:如果你使用字符串定義組件模板(例如:template: '...'
)、或者單文件組件(.vue)、或者<script>標簽(<script type="text/x-template">),那么你完全可以忽略掉這個限制。