第四节:Vuejs组件及组件之间的交互

 一. 组件及其交互

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>
View Code

运行效果:

 

 二. 购物车案例

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>
View Code

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 
原文地址:https://www.cnblogs.com/yaopengfei/p/12326466.html