一. 组件及其交互
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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。