vue.js學習之組件(下篇)

 本文的Demo和源代碼已放到GitHub,若是您以爲本篇內容不錯,請點個贊,或在GitHub上加個星星!javascript

https://github.com/zwl-jasmine95/Vue_testhtml

 如下全部知識都是基於vue.js 2.0版本vue


 

1、組件編譯做用域

<child-component> {{ message }}</child-component>

message 應該綁定到父組件的數據,組件做用域簡單地說是:java

父組件模板的內容在父組件做用域內編譯;子組件模板的內容在子組件做用域內編譯。node

組件的模板是在其做用域內編譯的,那麼組件選項對象中的數據也應該是在組件模板中使用的。
git

 1     <div id="component-demo">
 2         <!--  #component-demo是Vue實例掛載的元素,應該在掛載元素範圍內使用組件-->
 3         <hello-component></hello-component>
 4     </div>
 5 
 6 <script type="text/javascript">
 7     Vue.component('hello-component',{
 8         template:'<h1>hello component!</h1>'
 9     });
10     var vm = new Vue({
11         el:'#component-demo'
12     });
13 
14 </script>

在建立一個Vue實例時,除了將它掛載到某個HTML元素下,還要編譯組件,將組件轉換爲HTML片斷。
除此以外,Vue實例還會識別其所掛載的元素下的<hello-component>標籤,而後將<hello-component>標籤替換爲HTML片斷。github

實際上瀏覽器仍然是不理解<hello-component>標籤的,web

組件在使用前,通過編譯已經被轉換爲HTML片斷了,組件是有一個做用域的,那麼組件的做用域能夠將它理解爲組件模板包含的HTML片斷,組件模板內容以外就不是組件的做用域了。算法

例如,hello-component組件的做用域只是下面這個小片斷:數組

通俗地講,在子組件中定義的數據,只能用在子組件的模板。在父組件中定義的數據,只能用在父組件的模板。若是父組件的數據要在子組件中使用,則須要子組件定義props。

 


 

2、使用slot分發內容

 一、什麼是「內容分發」?

在使用組件時,每每會這樣:

<app>
  <app-header></app-header>
  <app-footer></app-footer>
</app>

注意兩點:

  1. <app> 組件不知道它會收到什麼內容。這是由使用 <app> 的父組件決定的。

  2. <app> 組件極可能有它本身的模版。

爲了讓組件能夠組合,咱們須要一種方式來混合父組件的內容與子組件本身的模板。這個過程被稱爲 內容分發。Vue.js 實現了一個內容分發 API,參照了當前 Web 組件規範草案,使用特殊的 <slot> 元素做爲原始內容的插槽。

 

二、單個slot

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>單個slot</title>
 6     <script type="text/javascript" src="../lib/js/vue.js"></script>
 7 </head>
 8 <body>
 9     <div id="demo">
10         <h1>我是父組件</h1>
11         <my-component>
12             <p>這是初始內容1</p>
13             <p>這是初始內容2</p>
14         </my-component>
15     </div>
16 
17     <template id="myComponent">
18         <div>
19             <h1>我是子組件的標題</h1>
20             <slot>沒有分發內容的時候纔會顯示</slot>
21         </div>
22     </template>
23 
24     <script type="text/javascript">
25 
26         Vue.component('my-component',{
27             template:'#myComponent'
28         });
29 
30         var vm = new Vue({
31             el:'#demo'
32         });
33     </script>
34 
35 </body>
36 </html>
View Code

結果:

 除非子組件模板包含至少一個 <slot> 插口,不然父組件的內容將會被丟棄(其餘狀況2)當子組件模板只有一個沒有屬性的 slot 時,父組件整個內容片斷將插入到 slot 所在的 DOM 位置,並替換掉 slot 標籤自己。

最初在 <slot> 標籤中的任何內容都被視爲備用內容。備用內容在子組件的做用域內編譯,而且只有在宿主元素爲空,且沒有要插入的內容時才顯示備用內容。

  •  其餘狀況1:刪除父組件模板中的內容

     

  •  其餘狀況2:刪除子組件模板裏的<slot>

  • 其餘狀況3:子組件裏有多個<slot>

 

(固然,這裏有兩個匿名<slot>會有警告,應該用特殊的屬性 name 來配置如何分發內容。詳見第三節 具名slot)

 

 三、具名slot

<slot> 元素能夠用一個特殊的屬性 name 來配置如何分發內容。多個 slot 能夠有不一樣的名字。具名 slot 將匹配內容片斷中有對應 slot 特性的元素。

仍然能夠有一個匿名 slot,它是默認 slot,做爲找不到匹配的內容片斷的備用插槽。若是沒有默認的 slot,這些找不到匹配的內容片斷將被拋棄。

 demo :

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>具名slot</title>
 6     <script type="text/javascript" src="../lib/js/vue.js"></script>
 7 </head>
 8 <body>
 9     <div id="demo">
10         <h1>我是父組件</h1>
11         <my-component>
12             <h1 slot="header">這裏多是一個頁面標題</h1>
13             <p>主要內容的一個段落。</p>
14             <p>另外一個主要段落。</p>
15             <p slot="footer">這裏有一些聯繫信息</p>
16         </my-component>
17     </div>
18 
19     <template id="myComponent">
20         <div class="container">
21             <header>
22                 <slot name="header"></slot>
23             </header>
24             <main>
25                 <slot>這裏是匿名slot</slot>
26             </main>
27             <footer>
28                 <slot name="footer"></slot>
29             </footer>
30         </div>
31     </template>
32 
33     <script type="text/javascript">
34 
35         Vue.component('my-component',{
36             template:'#myComponent'
37         });
38 
39         var vm = new Vue({
40             el:'#demo'
41         });
42     </script>
43 </body>
44 </html>
View Code

 

 

 四、做用域插槽

2.1.0新增

 做用域插槽是一種特殊類型的插槽,用做使用一個 (可以傳遞數據到) 可重用模板替換已渲染元素。

 1   <div class="parent">
 2         <child>
 3             <template scope="props">
 4                 <span>hello from parent</span>
 5                 <span>{{ props.text }}</span>
 6             </template>
 7         </child>
 8     </div>
 9 
10     <template id="myComponent">
11         <div class="child">
12             <slot text="hello from child">沒有分發內容的時候纔會顯示</slot>
13         </div>
14     </template>
15 
16     <script type="text/javascript">
17         Vue.component('child',{
18             template:'#myComponent'
19         });
20         
21         var vm = new Vue({
22             el:'.parent'
23         });
24     </script>

 

 在子組件中,只需將數據傳遞到插槽,就像你將 props 傳遞給組件同樣

在父級中,具備特殊屬性 scope 的 <template> 元素必須存在,表示它是做用域插槽的模板。scope 的值對應一個臨時變量名,此變量接收從子組件中傳遞的 props 對象

 

 做用域插槽更具表明性的用例是列表組件,容許組件自定義應該如何渲染列表每一項。做用域插槽也能夠是具名的。(線上demo

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>做用域插槽-列表</title>
 6     <script type="text/javascript" src="../lib/js/vue.js"></script>
 7 </head>
 8 <body>
 9     <div class="parent">
10         <my-list :items="items">
11             <template slot="item" scope="props">
12                 <li class="my-fancy-item">{{ props.text }}</li>
13             </template>
14         </my-list>
15     </div>
16 
17     <template id="myComponent">
18         <ul>
19             <slot name="item" v-for="item in items" :text="item.text"></slot>
20         </ul>
21     </template>
22 
23     <script type="text/javascript">
24         Vue.component('my-list',{
25             template:'#myComponent',
26             data:function () {
27                 return {
28                     items:[
29                         {id:1,text:'列表1'},
30                         {id:2,text:'列表2'},
31                         {id:3,text:'列表3'},
32                         {id:4,text:'列表4'}
33                     ]
34                 }
35             }
36         });
37 
38         var vm = new Vue({
39             el:'.parent',
40             data:{
41                 items:[]
42             }
43         });
44     </script>
45 
46 </body>
47 </html>
View Code

 

 

(這裏代碼中刪除兩處對效果並無什麼影響)


 

3、動態組件

 經過使用保留的 <component> 元素,動態地綁定到它的 is 特性,咱們讓多個組件可使用同一個掛載點,並動態切換:

var vm = new Vue({
  el: '#example',
  data: {
    currentView: 'component1'   //默認選中的組件
  },
  components: {
    component1: { /* ... */ },
    component2: { /* ... */ },
    component13: { /* ... */ }
  }
})
<component v-bind:is="currentView">
  <!-- 組件在 vm.currentview 變化時改變! -->
</component>

也能夠直接綁定到組件對象上:

var Home = {
  template: '<p>Welcome home!</p>'
}
var vm = new Vue({
  el: '#example',
  data: {
    currentView: Home
  }
})

經過具體實例來講明:demo

 1   <div class="container">
 2         <!--導航欄-->
 3         <ul class="nav nav-pills">
 4             <li><a href="javascript:void(0)" @click="toggleTab(0)">{{tabText1}}</a></li>
 5             <li><a href="javascript:void(0)" @click="toggleTab(1)">{{tabText2}}</a></li>
 6             <li><a href="javascript:void(0)" @click="toggleTab(2)">{{tabText3}}</a></li>
 7         </ul>
 8         <!-- 點擊導航後要切換的內容容器 -->
 9         <div class="content">
10             <!-- 若是把切換出去的組件保留在內存中,能夠保留它的狀態或避免從新渲染。爲此能夠添加一個 keep-alive 指令參數 -->
11             <keep-alive><component :is="currentView"></component></keep-alive>
12         </div>
13     </div>
14 
15     <!-- 點擊導航後要切換的內容 -->
16     <template id="tab-content1">
17         <div>這是第一個選項卡的內容!</div>
18     </template>
19 
20     <template id="tab-content2">
21         <div>這是第二個選項卡的內容!</div>
22     </template>
23 
24     <template id="tab-content3">
25         <div>這是第三個選項卡的內容!</div>
26     </template>
27 
28     <script type="text/javascript">
29         //局部註冊組件(選項卡內容)
30         var tab1 = {
31             template:'#tab-content1'
32         };
33         var tab2 = {
34             template:'#tab-content2'
35         };
36         var tab3 = {
37             template:'#tab-content3'
38         };
39         
40         var vm = new Vue({
41             el:'.container',
42             data:{
43                 tabText1:'選項卡1',
44                 tabText2:'選項卡2',
45                 tabText3:'選項卡3',
46                 currentView:tab1
47             },
48             //註冊局部組件
49             components:{
50                 tabComponent1:tab1,
51                 tabComponent2:tab2,
52                 tabComponent3:tab3
53             },
54             methods:{
55                 toggleTab:function (i) {
56                     var arr = ['tabComponent1','tabComponent2','tabComponent3'];
57                     this.currentView = arr[i];
58                 }
59             }
60         
61         })
62     </script>

若是把切換出去的組件保留在內存中,能夠保留它的狀態或避免從新渲染。爲此能夠添加一個 keep-alive 指令參數:

<keep-alive>
  <component :is="currentView">
    <!-- 非活動組件將被緩存! -->
  </component>
</keep-alive>

 

4、子組件索引

 儘管有 props 和 events,可是有時仍然須要在 JavaScript 中直接訪問子組件。爲此可使用 ref 爲子組件指定一個索引 ID。例如:

 1 <div id="parent">
 2         <user-profile ref="profile"></user-profile>
 3     </div>
 4 
 5     <script type="text/javascript">
 6         Vue.component('user-profile',{
 7             template:'<p>{{message}}</p>',
 8             data:function () {
 9                 return {
10                     message:'這裏是子組件索引!'
11                 }
12             }
13         });
14 
15         var parent = new Vue({
16             el: '#parent'
17         });
18         // 訪問子組件
19         var child = parent.$refs.profile;
20         console.log(child.$data);   //打印子組件的數據

當 ref 和 v-for 一塊兒使用時,ref 是一個數組,包含相應的子組件。

$refs 只在組件渲染完成後才填充,而且它是非響應式的。它僅僅做爲一個直接訪問子組件的應急方案——應當避免在模版或計算屬性中使用 $refs


 

5、遞歸組件

組件在它的模板內能夠遞歸地調用本身,不過,只有當它有 name 選項時才能夠

name: 'unique-name-of-my-component'

當你利用Vue.component全局註冊了一個組件, 全局的ID做爲組件的 name 選項,被自動設置.

Vue.component('unique-name-of-my-component', {
  // ...
})

若是你不謹慎, 遞歸組件可能致使死循環。要確保遞歸調用有終止條件 (好比遞歸調用時使用 v-if 並讓他最終返回 false)

實例:依次輸出123456789

(1)定義一個組件模板,基本標籤爲<span>0</span>,而後調用該組件。而且將數值加1(若是加1以後不超過10)。注意這些操做必定要放在一個標籤內,以下代碼中的div,不然會報錯。

(2)定義父組件,而且傳入初始count值

(3)註冊組件,而且定義v-if的成立條件

效果圖:

 

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>遞歸組件</title>
 6     <script type="text/javascript" src="../lib/js/vue.js"></script>
 7 </head>
 8 <body>
 9 <div id="parent">
10     <counter-component :count="0"></counter-component>
11 </div>
12 
13 <template id="counterComponent">
14     <div>
15         <span>{{count}}</span>
16         <counter-component :count="count+1" v-if="countNum">{{count}}</counter-component>
17     </div>
18 </template>
19 
20 <script type="text/javascript">
21     Vue.component('counter-component',{
22         name:'counter-component',
23         template:'#counterComponent',
24         props:['count'],
25         computed:{
26             countNum:function () {
27                 return this.count < 10;
28             }
29         }
30     });
31 
32     var parent = new Vue({
33         el: '#parent'
34     });
35 
36 </script>
37 </body>
38 </html>
View Code

 


6、組件間的循環使用

 假設有兩個組件稱爲 A 和 B,模塊系統看到它須要 A,可是首先 A 須要 B,可是 B 須要 A,而 A 須要 B,陷入了一個無限循環,所以不知道到底應該先解決哪一個。以下:

當使用Vue.component將這兩個組件註冊爲全局組件的時候,框架會自動爲你解決這個矛盾。
然而,若是使用諸如Webpack或者Browserify之類的模塊化管理工具來requiring/importing組件的話,就會報錯了。

要解決這個問題,咱們須要在其中一個組件中 (好比 A) 告訴模塊化管理系統,「A 雖然須要 B,可是不須要優先導入 B」

在咱們的例子中,咱們選擇在tree-folder 組件中來告訴模塊化管理系統循環引用的組件間的處理優先級,咱們知道引發矛盾的子組件是tree-folder-contents,因此咱們在beforeCreate 生命週期鉤子中去註冊它:

beforeCreate: function () {
  this.$options.components.TreeFolderContents = require('./tree-folder-contents.vue').default
}

7、組件綜合案例-樹形組件

 demo完成效果:

步驟:

一、首先在建立根實例時定義一組數據,在父組件上利用v-for指令循環數據,將每一項數據經過props傳遞給子組件(列表項li),並將數據的name值在li顯示。

 HTML:

 1 <div class="container">
 2     <ul class="list-group">
 3         <tree-node v-for="item in items" :parent_node="item" :key="item.name"></tree-node>
 4     </ul>
 5 </div>
 6 <template id="treeNode">
 7     <li class="list-group-item">
 8         <div>
 9             {{parent_node.name}}
10         </div>
11     </li>
12 </template>

JS:

 1 Vue.component('tree-node',{
 2         name:'tree-node',
 3         template:'#treeNode',
 4         props:['parent_node']
 5     });
 6 
 7     var vm = new Vue({
 8         el:'.container',
 9         data:{
10             items:[
11                 {
12                     name:'列表1'
13                 },
14                 {
15                     name:'列表2'
16                 },
17                 {
18                     name:'列表3'
19                 }
20             ]
21         }
22     })

注意:

key 的特殊屬性主要用在 Vue的虛擬DOM算法,在新舊nodes對比時辨識VNodes。若是不使用key,Vue會使用一種最大限度減小動態元素而且儘量的嘗試修復/再利用相同類型元素的算法。使用key,它會基於key的變化從新排列元素順序,而且會移除key不存在的元素。

有相同父元素的子元素必須有獨特的key。重複的key會形成渲染錯誤。最多見的用例是結合 v-for。

<ul>
  <li v-for="item in items" :key="item.id">...</li>
</ul>

二、給數據加一項data,用來存放二級列表數據;循環使用組件tree-node,而且將傳遞給v-for的列表項改成對應二級列表的列表項。

 1 var vm = new Vue({
 2         el:'.container',
 3         data:{
 4             items:[
 5                 {
 6                     name:'列表1',
 7                     data:[
 8                         {name:'列表1-1'},
 9                         {name:'列表1-2'},
10                         {name:'列表1-3'}
11                     ]
12                 },
13                 {
14                     name:'列表2',
15                     data:[
16                         {name:'列表2-1'},
17                         {name:'列表2-2'},
18                         {name:'列表2-3'}
19                     ]
20                 },
21                 {
22                     name:'列表3',
23                     data:[]
24                 }
25             ]
26         }
27     })
 1 <template id="treeNode">
 2     <li class="list-group-item">
 3         <div>
 4             {{parent_node.name}}
 5         </div>
 6         <ul>
 7             <tree-node v-for="child_node in parent_node.data" :parent_node="child_node" :key="child_node.name"></tree-node>
 8         </ul>
 9     </li>
10 </template>

此時效果以下:

三、給一級列表項數據添加open字段,用來顯示列表是否展開;

(我默認列表1展開,列表2和列表3不展開)

四、給一級列表添加兩個圖標(open和close圖標),用v-if來判斷哪個圖標顯示。兩個圖標的顯示條件都是列表項存在第二級列表數據-data。

 1 <template id="treeNode">
 2     <li class="list-group-item">
 3         <div>
 4             <span v-if="!parent_node.open && parent_node.data" class="glyphicon glyphicon-folder-close"></span>
 5             <span v-if="parent_node.open && parent_node.data" class="glyphicon glyphicon-folder-open"></span>
 6             {{parent_node.name}}
 7         </div>
 8         <ul>
 9             <tree-node v-for="child_node in parent_node.data" :parent_node="child_node" :key="child_node.name"></tree-node>
10         </ul>
11     </li>
12 </template>

 

此時效果爲:

五、用v-show和v-if給二級列表加上顯示條件。當沒有data數據的時候,二級列表不存在;當open字段爲false的時候,二級列表不顯示;

 

 1 <template id="treeNode">
 2         <li class="list-group-item">
 3             <div>
 4                 <span v-if="!parent_node.open && parent_node.data" class="glyphicon glyphicon-folder-close"></span>
 5                 <span v-if="parent_node.open && parent_node.data" class="glyphicon glyphicon-folder-open"></span>
 6                 {{parent_node.name}}
 7             </div>
 8 
 9             <ul v-if="parent_node.data" v-show="parent_node.open">
10                 <tree-node v-for="child_node in parent_node.data" :parent_node="child_node" :key="child_node.name"></tree-node>
11             </ul>
12 
13         </li>
14     </template>

 

此時顯示效果爲:

 

至此demo算是已經完成了,最後一步則是給一級列表加上點擊事件。

 

六、給列表項中的div加上點擊事件

 


 本文的Demo和源代碼已放到GitHub  https://github.com/zwl-jasmine95/Vue_test

若是您以爲本篇內容不錯,請點個贊,或在GitHub上加個星星!

相關文章
相關標籤/搜索