1.1全局組件註冊:Vue.component('didi-component',DIDIComponent)html
參數1('didi-component'):註冊組件的名稱,即在HTML中可使用對應名稱的自定義標籤來添加組件:<didi-component></didi-component>,名稱除了使用中劃線與html中添加自定義標籤一致之外,還可使用小駝峯命名方式來定義名稱,一樣vue內部會自動匹配到中劃線的html自定義標籤上,即‘didi-component’同等於‘didiComponent’,也有不規範的寫法直接自定義任意英文字符,不採用鏈接符(中劃線)也不採用小駝峯命名,也是能夠的,後面示例中會有出現。vue
參數2(DIDIComponent):註冊組件的鉤構造函數Function,也能夠是Object。數組
1 //組件構造器構造組件: 2 var MyComponent = Vue.extent({ 3 //選項... 4 }) 5 //傳入選項對象註冊全局組件: 6 Vue.component('didi-component',{ 7 template:`<div>A custom component!</div>` 8 })
實例代碼(代碼摺疊):瀏覽器
1 <div id="example"> 2 <didi-component></didi-component> 3 </div> 4 <script> 5 var DIDIComponent = Vue.extend({ 6 template:`<div>A custom component!</div>` 7 }) 8 //註冊 9 Vue.component('didi-component',DIDIComponent) 10 //建立vue根實例 11 new Vue({ 12 el:'#example' 13 }) 14 </script>
1.2局部組件註冊:也能夠說是在vue實例上註冊組件。緩存
1 <div id="example"> 2 <didi-component></didi-component> 3 </div> 4 <script> 5 //註冊局部組件 6 var DIDIComponent = Vue.extend({ 7 template:`<div>i am child!</div>` 8 }); 9 new Vue({ 10 el:'#example', 11 components:{ 12 didiComponent:DIDIComponent 13 } 14 }); 15 </script>
所謂局部組件就是不使用Vue.component()註冊組件,而是直接在vue實例上,經過component添加組件,上面的示例中將組件構造寫在實例外面,也能夠直接寫在實例中:app
1 new Vue({ 2 el:'#example', 3 components:{ 4 didiComponent:Vue.extend({ 5 template:`<div>i am child!</div>` 6 }) 7 } 8 });
注:如今的vue中構造組件能夠不用寫Vue.extend()方法,而是能夠直接寫成對象形式,後面的示例中將所有省略Vue.extend()。ide
全局組件與局部組件除了全局組件經過Vue.component()方法註冊,而局部組件直接經過component添加到vue實例對象上之外。全局組件註冊會始終以構造的形式被緩存,而局部組件不被使用時,不會被構造緩存,而是在vue實例須要時才被構造。雖說將組件構造緩存在內存中能夠提升代碼執行效率,可是另外一方面是消耗大量的內存資源。函數
除了上面的單個構造註冊,也能夠直接在模板中使用引用其餘局部組件:測試
1 <div id="example"> 2 <didi-component></didi-component> 3 </div> 4 <script> 5 var Child = { 6 template:`<div>i am child!</div>`, 7 replace:false 8 } 9 var Parent = { 10 template:`<div>//這個包裝元素如今不能使用template標籤了,之前能夠 11 <p>i am parent</p> 12 <br/> 13 <child-component></child-component> 14 </div>`, 15 components:{ 16 'childComponent':Child//在局部組件模板中直接引用其餘局部組件 17 } 18 } 19 new Vue({ 20 el:'#example', 21 components:{ 22 didiComponent:Parent 23 } 24 }) 25 </script>
2.1經過props實現數據傳輸:優化
經過v-bind在組件自定義標籤上添加HTML特性創建數據傳遞通道;
在組件字段props上綁定自定義標籤上傳遞過來的數據;
1 <div id="example"> 2 <!--將vue實例中的數據resultsList綁定到自定義特性list上--> 3 <didi-component :list="resultsList"></didi-component> 4 </div> 5 <script> 6 //綁定數據 7 var Child = { 8 props:['list'],//經過自定義特性list獲取父組件上的resultsList 9 template: `<div> 10 <p> 11 <span>姓名</span> 12 <span>語文</span> 13 <span>數學</span> 14 <span>英語</span> 15 </p> 16 <ul> 17 <li v-for="item in list" :key="item.name"> 18 <span>{{item.name}}</span> 19 <span>{{item.results.language}}</span> 20 <span>{{item.results.math}}</span> 21 <span>{{item.results.english}}</span> 22 </li> 23 </ul> 24 </div>` 25 } 26 var vm = new Vue({ 27 el:'#example', 28 components:{ 29 didiComponent:Child 30 }, 31 data:{ 32 resultsList:[ 33 { 34 name:"張三", 35 results:{language:89,math:95,english:90} 36 }, 37 { 38 name:"李四", 39 results:{language:92,math:76,english:80} 40 }, 41 { 42 name:"王五", 43 results:{language:72,math:86,english:98} 44 } 45 ] 46 } 47 }); 48 </script>
經過上面的示例能夠了解到子組件獲取父組件上的數據過程,這你必定會有一個疑問,props這裏這有什麼做用,爲何須要props這個字段?
a.每一個組件都有本身獨立的實例模型來管理本身身的屬性,經過props獲取到父組件中自身須要的數據,並使用自身屬性緩存數據引用能夠提升程序執行效率,若是將父組件上的數據全盤接收過來,對於子組件自身來講會有大量多餘的數據,反而下降程序執行效率。
b.經過props數據校驗,保證獲取到正確無誤的數據,提升程序的可靠性。
2.2props接收數據的方式及校驗處理:
1 //採用數組形式接收父組件經過自定義特性傳遞過來的數據 2 props:['data1','data2','data3'...]//數組接收方式只負責數據接收,並不對數據校驗處理 3 4 //採用對象形式接收父組件經過自定義特性傳遞過來的數據 5 props:{ 6 data1:{ 7 type:Array,//校驗接收數據類型爲Array,type的值還能夠是數組,添加數據可符合多種數據類型的校驗 8 default:[{name:"我是默認參數",...}],//當data1沒有接收到數據時,組件就會使用默認數據渲染 9 required:true,//校驗必須接收到數據,否則即便在有默認數據的狀況下也會在控制檯打印出報錯提示 10 validator(value){//校驗數據具體信息,參數value接收到的是傳遞過來的數據,若是方法返回false則表示數據不符合條件,控制檯打印出報錯提示 11 return value.length < 1; 12 } 13 }
採用數據校驗只能保證在數據有誤時在控制檯打印出相對準確的錯誤提示,並不會阻止數據渲染,也不會阻塞後面的代碼執行。
2.3棧內存傳值與單向數據流
1 <div id="example"> 2 父級對象傳值:<input type="text" v-model="info.name"/> 3 父級對象屬性傳值:<input type="text" v-model="obj.name"> 4 父級字符串傳值:<input type="text" v-model="name"> 5 <child v-bind:msg1.sync="info" v-bind:msg2="obj.name" v-bind:msg3="name"></child> 6 </div> 7 <script> 8 new Vue({ 9 el:'#example', 10 data:{ 11 info:{ 12 name:'順風車' 13 }, 14 obj:{ 15 name:"專車" 16 }, 17 name:"快車" 18 }, 19 components:{ 20 'child':{ 21 props:['msg1','msg2','msg3'], 22 template:` <div> 23 接收對象傳值:<input type="text" v-model="msg1.name"/> 24 接收對象屬性傳值:<input type="text" v-model="msg2"> 25 接收字符串傳值:<input type="text" v-model="msg3"> 26 </div>` 27 } 28 } 29 }) 30 </script>
示例效果:
經過示例能夠看到vue父子組件傳值,採用的是父級向子級的單向數據流,當父級數據發生變化時,子級數據會跟着變化。但同時又由於傳遞值的方式採用的是棧內存賦值方式,若是父級傳遞給子級的是引用值類型數據,在子級中改變數據也會引起父級數據的更改。
注:在權威指南中有說能夠經過給數據傳遞添加修飾符once和sync來控制數據的但雙向綁定,在2.x中測試無效,但添加這兩個修飾符不報錯。關於個問題我想應該是版本更新優化的,但不能肯定,畢竟我沒有使用過以前的版本,也沒有閱讀以前版本的手冊和源碼,這個一點暫時保留意見。
因爲vue父子組件傳值採用了棧內存傳值,就沒有辦法保證數據的單向傳遞,解決這個問題的辦法很簡單,在子組件中經過克隆該數據用自身的數據引用保存下來就OK了,但要注意在子組件中聲明data須要使用function類型。
注:即使傳遞的數據是原始值類型的數據,也不要直接使用接收的數據,雖然程序能正常運行,可是vue在控制檯對直接使用原始值的行爲報警告,因此正確的數據接收方式是經過props接收後在子組件自身的數據上再建立一個數據副原本使用。
data(){ return{ //自身數據引用名稱:接收父級傳遞來的引用數據的克隆數據 } }
在上一節介紹了父子組件數據傳遞,咱們知道vue採用的是單向數據流,這就意味着當子組件須要向父組件傳值時就得須要其餘手段,畢竟在更多時候子組件極可能會有複用性狀況,因此子組件也不能直接去更改父組件的數據,但總會有不少需求須要咱們將子組件的計算結果傳遞給父組件。
Vue經過在父級做用域上定義事件,而後再由子組件使用$emit來實現事件派送,當派送到對應事件做用域時,使用派送過來的參數(數據)觸發事件回調函數,這就是組件通訊。經過組件通訊能夠解決子組件向父組件傳值,示例:
1 <div id="example"> 2 <!-- 經過transmitdata接收來自子組件的傳輸指令,而後觸發父組件的addComData方法 --> 3 <comment-complate @transmitdata="addComData"></comment-complate> 4 <ul> 5 <li v-for="item in comList" :key="item.id"> 6 <div> 7 <span class="userName">{{item.userName}}</span> 8 <span class="comDate">{{item.date}}</span> 9 </div> 10 <div>{{item.val}}<div> 11 </li> 12 </ul> 13 </div> 14 <script> 15 var comment = { 16 template: `<div> 17 <textarea v-model="comVal"></textarea> 18 <p><button @click="submitData">提交</button></p> 19 </div>`, 20 data(){ 21 return { 22 userId:1001, 23 userName:"他鄉踏雪", 24 comVal:"" 25 } 26 }, 27 methods:{ 28 submitData(){ 29 console.log(this.comVal + 'a'); 30 var comDate = new Date(); 31 let comData = { 32 id:Number(''+this.userId + comDate.getTime()), 33 userName:this.userName, 34 date:comDate.toLocaleDateString() + ' ' + comDate.toLocaleTimeString(), 35 val:this.comVal 36 } 37 this.$emit('transmitdata',comData);//經過$emit監聽提交留言的點擊事件,被觸發後將數據傳遞給自定義方法transmitdata 38 } 39 } 40 } 41 var vm = new Vue({ 42 el:'#example', 43 components:{ 44 commentComplate:comment 45 }, 46 data:{ 47 comList:[ 48 { 49 userName:"南都谷主", 50 id:1001, 51 date:'2019/7/8 上午00:32:55', 52 val:'2017年,在長春園東南隅的如園遺址,工做人員在進行' 53 } 54 ] 55 }, 56 methods:{ 57 addComData(data){ 58 //transmitdata自定義事件接收到數據傳遞請求後,將傳遞過來的數據交給addComData處理 59 this.comList.unshift(data); 60 } 61 } 62 }); 63 </script>
若是用來接收數據傳輸數據的事件是一個原生的DOM事件,就沒必要使用$emit()來監聽事件觸發,只須要在原生的事件聲明後添加一個‘.navite’後綴就能夠自動實現監聽事件觸發,原生事件自己就是由瀏覽器自身監聽,因此沒必要要多餘的操做。如:@click.navite="父組件的事件監聽方法"。
<comment-complate @click="addComData"></comment-complate>
4.1插槽:<slot></slot>
有時候須要在父級組件中給子組件添加一些節點內容,這時候就能夠在使用slot來實現,而且還能夠在子組件中使用具名插槽,來實現指定位置插入節點。示例:
1 <div id="app"> 2 <child> 3 <span>{{titleName}}:</span><!--插入插槽--> 4 </child> 5 </div> 6 <script> 7 var vm = new Vue({ 8 el:'#app', 9 components:{ 10 child:{ 11 template:` <div> 12 <slot></slot><!--定義插槽--> 13 <input type="text" /> 14 </div>` 15 } 16 }, 17 data:{ 18 titleName:'郵箱' 19 } 20 }); 21 </script>
經過上面的插槽功能就能夠動態的切換輸入框的標題,在一些需求中有多種內容輸入方式,就不須要去定義多個組件來實現,只須要在父級組件來切換輸入標題,子組件只負責輸入操做,就能夠實如今同一個組件上實現多個輸入場景。
有了多種內容輸入場景就必然須要多種輸入提示,這種輸入提示一定是須要與輸入標題相配合,數據必然是一樣與標題內容處於父級組件上,就須要多個插槽來實現,這時候就須要用到具名插槽:
1 <div id="app"> 2 <child> 3 <span slot="title">{{titleName}}:</span><!--插入標題插槽--> 4 <span slot="hint">{{hint}}</span> 5 </child> 6 </div> 7 <script> 8 var vm = new Vue({ 9 el:'#app', 10 components:{ 11 child:{ 12 template:` <div> 13 <slot name="title"></slot><!--定義標籤具名插槽--> 14 <input type="text" /> 15 <slot name="hint"></slot><!--定義提示具名插槽--> 16 </div>` 17 } 18 }, 19 data:{ 20 titleName:'郵箱', 21 hint:"請輸入正確的郵箱地址" 22 } 23 }); 24 </script>
經過具名插槽能夠將父級插入的節點插入到指定的地方,固然這時候你會說能夠直接在子組件上來實現這些數據切換,這是確定可行的,可是使用子組件實現就必然會涉及到父子組件傳值。有可能你也會想到這樣的功能也可使用一個父組件就能夠實現,爲何還要使用子組件呢?這也固然是能夠的,可是這一樣涉及到了另外一個問題,若是是複雜一點的需求呢?因此,沒有絕對的設計優點,這要看具體需求,若是是在比較複雜的需求中就能夠經過插槽的方式將輸入操做與業務邏輯經過組件層級分離。
插槽除了上面的應用,在接下來的動態組件中也會有很大做用。
4.2動態組件:
所謂動態組件就是在父組件中定義一個子組件,能夠經過數據來切換實現引入不一樣的子組件,這時你必定會想這不就能夠經過v-if和v-else來實現嗎?注意,v-if和v-else來配合實現只能實現兩個組件切換,並且須要在父組件中引入兩個子組件,這與動態組件只須要引入一個子組件,而且能夠切換多個子組件的功能來比較相差甚遠。
語法:<component is=「綁定指定的子組件」></component>
示例:
1 <div id="app"> 2 <button @click="chanegCmp">切換組件</button> 3 <component :is="cmpType"></component> 4 </div> 5 <script> 6 const cmpOne = { 7 template:` <div> 8 <span>組件1:</span> 9 <input type="text" /> 10 </div>` 11 } 12 const cmpTwo = { 13 template:` <div> 14 <span>組件2:</span> 15 <input type="text" /> 16 </div>` 17 } 18 const cmpThree = { 19 template:` <div> 20 <span>組件3:</span> 21 <input type="text" /> 22 </div>` 23 } 24 var vm = new Vue({ 25 el:'#app', 26 data:{ 27 cmpList:["cmpOne","cmpTwo","cmpThree"], 28 cmpType:"cmpOne", 29 cmpPresent:0 30 }, 31 components:{ 32 cmpOne:cmpOne, 33 cmpTwo:cmpTwo, 34 cmpThree:cmpThree 35 }, 36 methods:{ 37 chanegCmp(){ 38 console.log(this.cmpPresent + ' - ' + this.cmpList.length) 39 if( (this.cmpPresent + 1) === this.cmpList.length){ 40 this.cmpPresent = 0; 41 this.cmpType = this.cmpList[this.cmpPresent]; 42 }else{ 43 this.cmpPresent ++; 44 this.cmpType = this.cmpList[this.cmpPresent]; 45 } 46 } 47 } 48 }); 49 </script>
示例實現效果:
雖然能夠經過動態組件切換組件,可是上面的示例可能還並不能是咱們想要的,由於在切換組件時並不能保留組件的狀態,好比咱們在第一次切換組件時,輸入文本後第二次切換回到這個組件時,輸入的文本並不能不被保留,也就是說從新切換回來的組件是一個全新的渲染組件,並不上次的原組件,
針對這種需求vue給我提供了一個標籤<keep-alive>,使用這個標籤包裹component標籤就能夠實現保留子組件的狀態了,因此示例中的代碼能夠這樣修改:
1 <keep-alive> 2 <component :is="cmpType"></component> 3 </keep-alive>
4.3做用域插槽
在前面的4.1中介紹了插槽,它能夠在父級來定義相對靈活的子組件,在前面的示例中只是介紹了父級在子級指定爲節點位置插入一些元素節點,這時我想應該又引起了咱們的另外一個思考,既然插槽能夠在父級做用定義子級特定位置的節點,那可不能夠實現父級做用域定義子級特定的數據模板呢?也就是子級不只能夠提供一個佔位,還能夠提供數據引用來實如今父級組件中定義元素的節點和數據渲染。這個光是用文字來描述會有點繞,先來看一個小示例:
1 <div id="app"> 2 <child :userdata="user"><!--獲取父組件的數據user,並定義新的引用名稱userdata--> 3 <template slot-scope="list"><!--數據模板上經過slot-scope特性接收插槽傳來的數據,並定義索引名list--> 4 <span>{{list.username}} -- {{list.userCom}}</span><!--經過模板接收到的數據索引取出數據並渲染到頁面--> 5 </template> 6 </child> 7 </div> 8 <script> 9 const child = { 10 props:["userdata"],//接收父組件的數據userdata, 11 data(){ 12 return{ 13 comData:"child" 14 } 15 }, 16 template:` <div> 17 <!--經過插槽將接收到父組件的數據和子組件自身的數據傳給模板--> 18 <!--傳遞數據的方式就是將數據以特性的方式傳入slot標籤--> 19 <slot 20 :username="userdata.name" 21 :userCom="comData"></slot> 22 <input type="text"/> 23 </div>` 24 } 25 var vm = new Vue({ 26 el:'#app', 27 data:{ 28 user:{ 29 name:"他鄉踏雪", 30 } 31 }, 32 components:{ 33 child:child 34 } 35 }); 36 </script>
這裏的一個關鍵就是經過<slot>插槽標籤的特性鍵數據傳出,而後經過模板特性slot-scope接收這些數據,再來提供一個示例來進一步瞭解做用域插槽:
1 <div id="app"> 2 <cmp-two :list="list"> 3 <template slot-scope="list"> 4 <li>{{list.item}} - {{list.index}}</li> 5 </template> 6 </cmp-two> 7 <cmp-two :list="list"> 8 <template slot-scope="list"> 9 <li>{{list.index}} - {{list.item}}</li> 10 </template> 11 </cmp-two> 12 </div> 13 <script> 14 const cmpTwo = { 15 props:['list'], 16 template:`<div> 17 組件2:<input tyle="text"> 18 <ul> 19 <slot v-for="(item,index) in list" 20 :item="item" 21 :index="index"> 22 </slot> 23 </ul> 24 </div>` 25 } 26 var vm = new Vue({ 27 el:'#app', 28 components:{ 29 cmpTwo 30 }, 31 data:{ 32 list:[1,2,3,4,5] 33 } 34 }); 35 </script>