一. 組件及其交互
1.組件的注冊
(1).全局注冊
Vue.component('組件名稱', { }) 第1個參數是標簽名稱,第2個參數是一個選項對象。
選項參數包括
data:必須是一個function,然后要return,在return里面聲明變量。
template: 用``符號包裹
methods: 聲明方法
注意事項:
A. 模板必須是單個根元素
B. 如果使用駝峰式命名組件,那么在使用組件的時候,只能在另一個組件字符串模板中用駝峰的方式使用組件,在普通標簽中直接使用的話,必須加短橫線。如: HelloWord模板,在body里用的時候必須<hello-word>,而在btn-counter組件中,則可以直接使用<HelloWord>
總之:不推薦使用駝峰命名,建議小寫+短橫線。
Vue.component('HelloWord', {
template: `<div>我是HelloWord組件</div>`
});
Vue.component('btn-counter', {
data: function() {
return {
count: 0
}
},
template: `
<div>
<div>{{count}}</div>
<button @click="handle">點擊了{{count}}次</button>
<HelloWord></HelloWord>
</div>
`,
methods: {
handle: function() {
this.count += 3;
}
}
});
<p>1.全局組件</p> <btn-counter></btn-counter> <hello-word></hello-word>
(2).局部組件
在Vue實例中components進行聲明局部組件,僅供該實例使用。

2.父組件向子組件傳值
父組件發送形式是以屬性的形式綁定在子組件上,可以直接往里傳值,也可以用:符號進行綁定變量。然后子組件用屬性props接收,props是一個數組。
注意事項:
A.在props使用駝峰形式,在頁面中需要使用短橫線的形式字符串, 而在字符串模板中則沒有這個限制。
PS:這里面不演示了,總之在vue中不建議使用駝峰命名。
B.比如p3是props里聲明的一個屬性,在使用的時候 p3:'true' 這種情況p3是string類型; 而 :p3:'true',這種情況p3是布爾類型。
3.子組件向父組件傳值
子組件用 $emit() 觸發事件,第一個參數為 自定義的事件名稱 第二個參數為需要傳遞的數據(這里最多只能傳遞一個參數,但他可以是一個對象哦,對象里面包括很多屬性,如下:),父組件用v-on(或者@) 監聽子組件的事件,這里用固定命名 $event 來獲取子組件傳遞過來的參數。
this.$emit('change-num', { id: id, type: 'change', num: 10 });
4.兄弟組件之間的交互
兄弟之間傳遞數據需要借助於事件中心,通過事件中心傳遞數據,提供事件中心 var hub = new Vue()。
(1).傳遞數據方,通過一個事件觸發hub.$emit(方法名,傳遞的數據),這里可以傳遞多個數據哦
(2).接收數據方,通過mounted(){} 鈎子中 觸發hub.$on()方法名,這里可以接收多個數據哦
(3).銷毀事件 通過hub.$off()方法名銷毀之后無法進行傳遞數據
5.插槽
組件的最大特性就是復用性,而用好插槽能大大提高組件的可復用能力,在template中使用<slot>標簽
(1).匿名插槽
使用時,組件標簽中嵌套的內容(包含html)會替換掉slot; 如果不傳值 ,則使用 slot 中的默認值。
(2).具名插槽
使用時,通過slot屬性來指定, 這個slot的值必須和下面slot組件得name值對應上 如果沒有匹配到 則放到匿名的插槽中
特別注意:具名插槽的渲染順序,完全取決於模板中的順序,而不是取決於父組件中元素的順序!
(3).作用域插槽
A. 作用域插槽使用場景
a.父組件對子組件加工處理
b.既可以復用子組件的slot,又可以使slot內容不一致
B.作用域插槽的使用
a.子組件模板中,<slot>元素上有一個類似props傳遞數據給組件的寫法msg="xxx
b.插槽可以提供一個默認內容,如果如果父組件沒有為這個插槽提供了內容,會顯示默認的內容。 如果父組件為這個插槽提供了內容,則默認的內容會被替換掉
完整代碼如下:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title>06-組件及交互</title> 6 <style type="text/css"> 7 p { 8 font-size: 20px; 9 color: #0000FF; 10 font-weight: bold; 11 } 12 .current { 13 color: orange; 14 } 15 </style> 16 </head> 17 <body> 18 <div id="myApp"> 19 <p>1.全局組件</p> 20 <btn-counter></btn-counter> 21 <hello-word></hello-word> 22 <p>2.局部組件</p> 23 <ypfzj1></ypfzj1> 24 <p>3.父組件向子組件傳值</p> 25 <father-child p1="ypf1" :p2="p2" p3="true"></father-child> 26 <father-child p1="ypf1" :p2="p2" :p3="true"></father-child> 27 <p>4.子組件向父組件傳值</p> 28 <div :style="{fontSize:myFontSize+'px'}">我是內容,等着被控制</div> 29 <child-father @exchangebig='handle1($event)'></child-father> 30 <p>5.兄弟組件相互交互</p> 31 <brother-one></brother-one> 32 <brother-two></brother-two> 33 <button @click="destoryHandle">銷毀事件</button> 34 <p>6.1 匿名插槽</p> 35 <my-tips1>您超標了</my-tips1> 36 <my-tips1>用戶名不正確</my-tips1> 37 <my-tips1></my-tips1> 38 <p>6.2 具名插槽</p> 39 <my-tips2> 40 <div slot='header'>我是header</div> 41 <div>我是內容1</div> 42 <div>我是內容2</div> 43 <div slot='footer'>我是footer</div> 44 </my-tips2> 45 <my-tips2> 46 <div slot='footer'>我是footer</div> 47 <div>我是內容1</div> 48 <div slot='header'>我是header</div> 49 <div>我是內容2</div> 50 </my-tips2> 51 <p>6.3 作用域插槽</p> 52 <my-tips3 :list='list'> 53 <template slot-scope='slotProps'> 54 <strong v-if='slotProps.info.id==3' class="current">{{slotProps.info.name}}</strong> 55 <span v-else>{{slotProps.info.name}}</span> 56 </template> 57 </my-tips3> 58 </div> 59 60 <script src="js/vue.min.js" type="text/javascript" charset="utf-8"></script> 61 <script type="text/javascript"> 62 //全局組件 63 Vue.component('HelloWord', { 64 template: `<div>我是HelloWord組件</div>` 65 }); 66 Vue.component('btn-counter', { 67 data: function() { 68 return { 69 count: 0 70 } 71 }, 72 template: ` 73 <div> 74 <div>{{count}}</div> 75 <button @click="handle">點擊了{{count}}次</button> 76 <HelloWord></HelloWord> 77 </div> 78 `, 79 methods: { 80 handle: function() { 81 this.count += 3; 82 } 83 } 84 }); 85 //父組件向子組件中傳值 86 Vue.component('father-child', { 87 props: ['p1', 'p2', 'p3'], 88 data: function() { 89 return { 90 msg: '我是用來看父組件向子組件中傳值的' 91 } 92 }, 93 template: ` 94 <div> 95 <div>{{msg+"--"+p1+"--"+p2+"--"+p3}}</div> 96 <div>p3的類型為:{{typeof p3}}</div> 97 </div> ` 98 }); 99 100 //子組件向父組件傳值 101 Vue.component('child-father', { 102 props: [], 103 data: function() { 104 return { 105 msg: '我是用來看父組件向子組件中傳值的' 106 } 107 }, 108 template: ` 109 <div> 110 <button @click='$emit("exchangebig",5)'>增大測試1</button> 111 <button @click='$emit("exchangebig",10)'>增大測試2</button> 112 </div> ` 113 }); 114 //兩個兄弟組件 115 //事件中心 116 var hub = new Vue(); 117 Vue.component('brother-one', { 118 data: function() { 119 return { 120 num: 0 121 } 122 }, 123 template: ` 124 <div> 125 <div>borther1:{{num}}</div> 126 <div> 127 <button @click='handle'>控制兄弟brother2</button> 128 </div> 129 </div> 130 `, 131 methods: { 132 handle: function() { 133 //傳遞數據方,通過一個事件觸發hub.$emit(方法名,傳遞的數據) 觸發兄弟組件的事件 134 hub.$emit('yChild2Event', 2, 1); 135 } 136 }, 137 //Dom創建后 138 mounted: function() { 139 //接收數據方,通過mounted(){} 鈎子中 觸發hub.$on(方法名) 140 hub.$on('yChild1Event', (val1, val2) => { 141 this.num += val1 + val2; 142 }); 143 } 144 }) 145 Vue.component('brother-two', { 146 data: function() { 147 return { 148 num: 0 149 } 150 }, 151 template: ` 152 <div> 153 <div>borther2:{{num}}</div> 154 <div> 155 <button @click='handle'>控制兄弟brother1</button> 156 </div> 157 </div> 158 `, 159 methods: { 160 handle: function() { 161 //傳遞數據方,通過一個事件觸發hub.$emit(方法名,傳遞的數據) 觸發兄弟組件的事件 162 hub.$emit('yChild1Event', 4, 3); 163 } 164 }, 165 //Dom創建后 166 mounted: function() { 167 //接收數據方,通過mounted(){} 鈎子中 觸發hub.$on(方法名) 168 hub.$on('yChild2Event', (val1, val2) => { 169 this.num += val1 + val2; 170 }); 171 } 172 }) 173 //匿名插槽 174 Vue.component('my-tips1',{ 175 template:` 176 <div> 177 <strong>提醒:</strong> 178 <slot>默認內容</slot> 179 <slot>默認內容1</slot> 180 </div> 181 `, 182 }) 183 //具名插槽 184 Vue.component('my-tips2', { 185 template: ` 186 <div> 187 <div>我是具名插槽</div> 188 <slot name='header'></slot> 189 <slot></slot> 190 <slot name='footer'></slot> 191 </div> 192 ` 193 }); 194 //作用域插槽 195 Vue.component('my-tips3', { 196 props: ['list'], 197 template: ` 198 <div> 199 <li :key='item.id' v-for='item in list'> 200 <slot :info='item'>{{item.name}}</slot> 201 </li> 202 </div> 203 ` 204 }); 205 206 //放在下面的局部組件里 207 var ypfzj1 = { 208 data: function() { 209 return { 210 msg: '我是局部組件1' 211 } 212 }, 213 template: '<div>{{msg}}</div>' 214 }; 215 216 //Vm實例 217 var vm = new Vue({ 218 el: '#myApp', 219 data: { 220 p2: 'ypf2', 221 myFontSize: 12, 222 list: [{ 223 id: 1, 224 name: 'apple' 225 },{ 226 id: 2, 227 name: 'orange' 228 },{ 229 id: 3, 230 name: 'banana' 231 }] 232 }, 233 methods: { 234 handle1: function(val) { 235 this.myFontSize += val; 236 }, 237 //銷毀事件 238 destoryHandle: function() { 239 hub.$off('yChild1Event'); 240 hub.$off('yChild2Event'); 241 } 242 }, 243 components: { 244 'ypfzj1': ypfzj1, 245 } 246 }); 247 </script> 248 </body> 249 </html>
運行效果:


二. 購物車案例
1.需求分析
實現購物車商品列表的加載,數量的增加和減少、物品的刪除、及其對應總價的變化。
效果圖如下:

2. 實戰演練
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>組件之購物車案例</title> 6 <style type="text/css"> 7 .container { 8 } 9 .container .cart { 10 width: 300px; 11 margin: auto; 12 } 13 .container .title { 14 background-color: lightblue; 15 height: 40px; 16 line-height: 40px; 17 text-align: center; 18 /*color: #fff;*/ 19 } 20 .container .total { 21 background-color: #FFCE46; 22 height: 50px; 23 line-height: 50px; 24 text-align: right; 25 } 26 .container .total button { 27 margin: 0 10px; 28 background-color: #DC4C40; 29 height: 35px; 30 width: 80px; 31 border: 0; 32 } 33 .container .total span { 34 color: red; 35 font-weight: bold; 36 } 37 .container .item { 38 height: 55px; 39 line-height: 55px; 40 position: relative; 41 border-top: 1px solid #ADD8E6; 42 } 43 .container .item img { 44 width: 45px; 45 height: 45px; 46 margin: 5px; 47 } 48 .container .item .name { 49 position: absolute; 50 width: 90px; 51 top: 0;left: 55px; 52 font-size: 16px; 53 } 54 55 .container .item .change { 56 width: 100px; 57 position: absolute; 58 top: 0; 59 right: 50px; 60 } 61 .container .item .change a { 62 font-size: 20px; 63 width: 30px; 64 text-decoration:none; 65 background-color: lightgray; 66 vertical-align: middle; 67 } 68 .container .item .change .num { 69 width: 40px; 70 height: 25px; 71 } 72 .container .item .del { 73 position: absolute; 74 top: 0; 75 right: 0px; 76 width: 40px; 77 text-align: center; 78 font-size: 40px; 79 cursor: pointer; 80 color: red; 81 } 82 .container .item .del:hover { 83 background-color: orange; 84 } 85 </style> 86 </head> 87 <body> 88 <div id="app"> 89 <div class="container"> 90 <my-cart></my-cart> 91 </div> 92 </div> 93 <script type="text/javascript" src="js/vue.js"></script> 94 <script type="text/javascript"> 95 96 var CartTitle = { 97 props: ['uname'], 98 template: ` 99 <div class="title">{{uname}}的商品</div> 100 ` 101 } 102 103 var CartList = { 104 props: ['list'], 105 template: ` 106 <div> 107 <div :key='item.id' v-for='item in list' class="item"> 108 <img :src="item.img"/> 109 <div class="name">{{item.name}}</div> 110 <div class="change"> 111 <a href="" @click.prevent='sub(item.id)'>-</a> 112 <input type="text" class="num" :value='item.num' @blur='changeNum(item.id, $event)'/> 113 <a href="" @click.prevent='add(item.id)'>+</a> 114 </div> 115 <div class="del" @click='del(item.id)'>×</div> 116 </div> 117 </div> 118 `, 119 methods: { 120 changeNum: function(id, event){ 121 //向父組件傳值 122 this.$emit('change-num', { 123 id: id, 124 type: 'change', 125 num: event.target.value 126 }); 127 }, 128 sub: function(id){ 129 //向父組件傳值 130 this.$emit('change-num', { 131 id: id, 132 type: 'sub' 133 }); 134 }, 135 add: function(id){ 136 //向父組件傳值 137 this.$emit('change-num', { 138 id: id, 139 type: 'add' 140 }); 141 }, 142 del: function(id){ 143 // 把id傳遞給父組件 144 this.$emit('cart-del', id); 145 } 146 } 147 } 148 149 var CartTotal = { 150 props: ['list'], 151 template: ` 152 <div class="total"> 153 <span>總價:{{total}}</span> 154 <button>結算</button> 155 </div> 156 `, 157 computed: { 158 total: function() { 159 // 計算商品的總價 160 var t = 0; 161 this.list.forEach(item => { 162 t += item.price * item.num; 163 }); 164 return t; 165 } 166 } 167 } 168 169 Vue.component('my-cart',{ 170 data: function() { 171 return { 172 uname: '張三', 173 list: [{ 174 id: 1, 175 name: 'TCL彩電', 176 price: 1000, 177 num: 1, 178 img: 'img/a.jpg' 179 },{ 180 id: 2, 181 name: '機頂盒', 182 price: 1000, 183 num: 1, 184 img: 'img/b.jpg' 185 },{ 186 id: 3, 187 name: '海爾冰箱', 188 price: 1000, 189 num: 1, 190 img: 'img/c.jpg' 191 },{ 192 id: 4, 193 name: '小米手機', 194 price: 1000, 195 num: 1, 196 img: 'img/d.jpg' 197 },{ 198 id: 5, 199 name: 'PPTV電視', 200 price: 1000, 201 num: 2, 202 img: 'img/e.jpg' 203 }] 204 } 205 }, 206 template: ` 207 <div class='cart'> 208 <cart-title :uname='uname'></cart-title> 209 <cart-list :list='list' @change-num='changeNum($event)' @cart-del='delCart($event)'></cart-list> 210 <cart-total :list='list'></cart-total> 211 </div> 212 `, 213 components: { 214 'cart-title': CartTitle, 215 'cart-list': CartList, 216 'cart-total': CartTotal 217 }, 218 methods: { 219 changeNum: function(val) { 220 // 分為三種情況:輸入域變更、加號變更、減號變更 221 if(val.type=='change') { 222 // 根據子組件傳遞過來的數據,跟新list中對應的數據 223 this.list.some(item=>{ 224 if(item.id == val.id) { 225 item.num = val.num; 226 // 終止遍歷 227 return true; 228 } 229 }); 230 }else if(val.type=='sub'){ 231 // 減一操作 232 this.list.some(item=>{ 233 if(item.id == val.id) { 234 item.num -= 1; 235 // 終止遍歷 236 return true; 237 } 238 }); 239 }else if(val.type=='add'){ 240 // 加一操作 241 this.list.some(item=>{ 242 if(item.id == val.id) { 243 item.num += 1; 244 // 終止遍歷 245 return true; 246 } 247 }); 248 } 249 }, 250 delCart: function(id) { 251 // 根據id刪除list中對應的數據 252 // 1、找到id所對應數據的索引 253 var index = this.list.findIndex(item=>{ 254 return item.id == id; 255 }); 256 // 2、根據索引刪除對應數據 257 this.list.splice(index, 1); 258 } 259 } 260 }); 261 262 263 264 var vm = new Vue({ 265 el: '#app', 266 data: { 267 268 } 269 }); 270 271 </script> 272 </body> 273 </html>
!
- 作 者 : Yaopengfei(姚鵬飛)
- 博客地址 : http://www.cnblogs.com/yaopengfei/
- 聲 明1 : 如有錯誤,歡迎討論,請勿謾罵^_^。
- 聲 明2 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。
