一、组件
- 组件 (Component) 是 Vue.js 最弱小的性能之一
- 组件能够扩大 HTML 元素,封装可重用的代码
二、 组件注册
2.1 全局注册
Vue.component(组件名称, { data: 组件数据, template: 组件模板内容 })
- 全局组件注册后,任何vue实例都能够用
组件根底用法
<组件名称></组件名称>
<div id="example"> <!-- 2、 组件应用 组件名称 是以HTML标签的模式应用 --> <my-component></my-component></div><script> // 注册组件 // 1、 my-component 就是组件中自定义的标签名 Vue.component('my-component', { template: '<div>A custom component!</div>' }) // 创立根实例 new Vue({ el: '#example' })</script>
2.2 组件注意事项
- 组件参数的data值必须是函数,同时这个函数要求返回一个对象
- 组件模板必须是单个根元素
- 组件模板的内容能够是模板字符串(用反引号蕴含起来)
<div id="app"> <!-- 4、组件能够重复使用屡次 因为data中返回的是一个对象所以每个组件中的数据是公有的 即每个实例能够保护一份被返回对象的独立的拷贝 --> <button-counter></button-counter> <button-counter></button-counter> <button-counter></button-counter> <!-- 8、必须应用短横线的形式应用组件 --> <hello-world></hello-world> </div><script type="text/javascript"> //5 如果应用驼峰式命名组件,那么在应用组件的时候,只能在字符串模板中用驼峰的形式应用组件, // 7、然而在一般的标签模板中,必须应用短横线的形式应用组件 Vue.component('HelloWorld', { data: function(){ return { msg: 'HelloWorld' } }, template: '<div>{{msg}}</div>' }); Vue.component('button-counter', { // 1、组件参数的data值必须是函数 // 同时这个函数要求返回一个对象 data: function(){ return { count: 0 } }, // 2、组件模板必须是单个根元素 // 3、组件模板的内容能够是模板字符串 template: ` <div> <button @click="handle">点击了{{count}}次</button> <button>测试123</button> # 6 在字符串模板中能够应用驼峰的形式应用组件 <HelloWorld></HelloWorld> </div> `, methods: { handle: function(){ this.count += 2; } } }) var vm = new Vue({ el: '#app', data: { } }); </script>
2.3 命名形式
- 短横线形式(举荐)
Vue.component('my-component', { /* ... */ })
- 驼峰形式
Vue.component('MyComponent, { /* ... */ })
留神:驼峰形式命名的组件 <my-component> 和 <MyComponent> 个别状况都是可承受的,比方在字符串模版中应用。但间接在 DOM 中应用时只有 <my-component>是无效的。
2.4 部分注册
- 只能在以后注册它的vue实例中应用
<div id="app"> <my-component></my-component> </div><script> // 定义组件的模板 var Child = { data: function(){ return { msg: 'Hello' } }, template: '<div>A custom component!{{msg}}</div>' } new Vue({ el: '#app', //部分注册组件 components: { // <my-component> 将只在父模板可用 肯定要在实例上注册了能力在html文件中应用 'my-component': Child } }) </script>
三、Vue 调试工具
链接:https://pan.baidu.com/s/139hspAnspD7bJbo81xigmg 明码:1hsv
四、 Vue组件之间传值(数据交互)
4.1 父组件向子组件传值
- 子组件外部通过props属性接管传递过去的值
Vue.component(‘menu-item', { props: ['title'], template: '<div>{{ title }}</div>'})
- 父组件通过属性将值传递给子组件
能够传递静态数据<menu-item title="来自父组件的数据"></menu-item> 也能够动静绑定<menu-item :title="title"></menu-item>
- 在props中定义要接管的属性名时应用驼峰模式,在DOM中间接引入组件时须要应用短横线的模式,字符串模式的模板中则没有这个限度
Vue.component('third-com', { // 在 JavaScript 中是驼峰式的 props: ['testTitle'], template: '<div>{{ testTitle }}'})Vue.component('menu-item', { // 在 JavaScript 中是驼峰式的 props: ['menuTitle'], template: '<div>{{ menuTitle }}<third-com testTitle="hello"></div>' //字符串模版内驼峰式应用属性没有影响})<!– 在html中是短横线形式的 --><menu-item menu-title=“nihao"></menu-item>
- 传值类型
如果子组件须要失去原始类型,比方数字型、布尔型,在父组件中举荐应用:属性=属性值
的模式进行绑定,否则都会传递字符串类型。
<div id="app"> <div>{{pmsg}}</div> <menu-item :pstr='pstr' :pnum='12' :pboo='true' :parr='parr' :pobj='pobj'></menu-item> </div> Vue.component('menu-item', { props: ['pstr', 'pnum', 'pboo', 'parr', 'pobj'], template: ` <div> <div>{{pstr}}</div> <div>{{12 + pnum}}</div> <div>{{typeof pboo}}</div> <ul> <li :key='index' v-for='(item,index) in parr'>{{item}}</li> </ul> <span>{{pobj.name}}</span> <span>{{pobj.age}}</span> </div> </div> ` }); var vm = new Vue({ el: '#app', data: { pmsg: '父组件中内容', pstr: 'hello', parr: ['apple', 'orange', 'banana'], pobj: { name: 'lisi', age: 12 } } });
实例
<div id="app"> <div>{{pmsg}}</div> <!--1、menu-item 在 APP中嵌套着 故 menu-item 为 子组件 --> <!-- 给子组件传入一个动态的值 --> <menu-item title='来自父组件的值'></menu-item> <!-- 2、 须要动静的数据的时候 须要属性绑定的模式设置 此时 ptitle 来自父组件data 中的数据 . 传的值能够是数字、对象、数组等等 --> <menu-item :title='ptitle' content='hello'></menu-item> </div> <script type="text/javascript"> Vue.component('menu-item', { // 3、 子组件用属性props接管父组件传递过去的数据 props: ['title', 'content'], data: function() { return { msg: '子组件自身的数据' } }, template: '<div>{{msg + "----" + title + "-----" + content}}</div>' }); var vm = new Vue({ el: '#app', data: { pmsg: '父组件中内容', ptitle: '动静绑定属性' } }); </script>
4.2 子组件向父组件传值
- 子组件通过自定义事件向父组件传递信息,用
$emit()
触发事件<button @click='$emit("自定义事件名称","须要传递的数据")'>扩充字体</button>
- 父组件用
v-on
监听子组件的事件
间接写事件处理逻辑时应用数据<menu-item v-on:自定义事件名称='fontSize += $event'></menu-item>
绑定函数时应用<menu-item @自定义事件名称='函数名($event)'></menu-item>
<div id="app"> <div :style='{fontSize: fontSize + "px"}'>{{pmsg}}</div> <!-- 2 父组件用v-on 监听子组件的事件 这里 enlarge-text 是从 $emit 中的第一个参数对应 handle 为对应的事件处理函数 --> <menu-item :parr='parr' @enlarge-text='handle($event)'></menu-item> </div> <script type="text/javascript" src="js/vue.js"></script> <script type="text/javascript"> /* 子组件向父组件传值-携带参数*/ Vue.component('menu-item', { props: ['parr'], template: ` <div> <ul> <li :key='index' v-for='(item,index) in parr'>{{item}}</li> </ul> ### 1、子组件用$emit()触发事件 ### 第一个参数为 自定义的事件名称 第二个参数为须要传递的数据 <button @click='$emit("enlarge-text", 5)'>扩充父组件中字体大小</button> <button @click='$emit("enlarge-text", 10)'>扩充父组件中字体大小</button> </div> ` }); var vm = new Vue({ el: '#app', data: { pmsg: '父组件中内容', parr: ['apple','orange','banana'], fontSize: 10 }, methods: { handle: function(val){ // 扩充字体大小 this.fontSize += val; } } }); </script>
4.3 兄弟之间的传递
兄弟之间传递数据须要借助于事件核心,通过事件核心传递数据
- 提供事件核心
var eventHub = new Vue()
- 接收数据方,通过
mounted(){}
钩子中设置监听(注册)事件eventHub.$on('办法名', 数据形参)
- 传递数据方触发事件
eventHub.$emit(‘办法名', 传递的数据)
- 销毁事件,通过
eventhub.$off()
销毁之后,无奈进行传递数据eventHub.$off('办法名')
<div id="app"> <div>父组件</div> <div> <button @click='handle'>销毁事件</button> </div> <test-tom></test-tom> <test-jerry></test-jerry> </div> <script type="text/javascript" src="js/vue.js"></script> <script type="text/javascript"> /* 兄弟组件之间数据传递 */ //1、 提供事件核心 var hub = new Vue(); Vue.component('test-tom', { data: function(){ return { num: 0 } }, template: ` <div> <div>TOM:{{num}}</div> <div> <button @click='handle'>点击</button> </div> </div> `, methods: { handle: function(){ //2、传递数据方,通过一个事件触发hub.$emit(办法名,传递的数据) 触发兄弟组件的事件 hub.$emit('jerry-event', 2); } }, mounted: function() { // 3、接收数据方,通过mounted(){} 钩子中 触发hub.$on(办法名 hub.$on('tom-event', (val) => { this.num += val; }); } }); Vue.component('test-jerry', { data: function(){ return { num: 0 } }, template: ` <div> <div>JERRY:{{num}}</div> <div> <button @click='handle'>点击</button> </div> </div> `, methods: { handle: function(){ //2、传递数据方,通过一个事件触发hub.$emit(办法名,传递的数据) 触发兄弟组件的事件 hub.$emit('tom-event', 1); } }, mounted: function() { // 3、接收数据方,通过mounted(){} 钩子中 触发hub.$on()办法名 hub.$on('jerry-event', (val) => { this.num += val; }); } }); var vm = new Vue({ el: '#app', data: { }, methods: { handle: function(){ //4、销毁事件 通过hub.$off()办法名销毁之后无奈进行传递数据 hub.$off('tom-event'); hub.$off('jerry-event'); } } }); </script>
五、 组件插槽
指父组件向子组件传递模版内容,子组件预留的地位(template中的slot)就叫插槽。而传递给slot的内容就是组件标签之间的内容。
组件的最大个性就是复用性,而用好插槽能大大提高组件的可复用能力
5.1 匿名插槽
<div id="app"> <!-- 这里的所有组件标签中嵌套的内容会替换掉slot 如果不传值 则应用 slot 中的默认值 --> <alert-box>有bug产生</alert-box> <alert-box>有一个正告</alert-box> <alert-box></alert-box> </div> <script type="text/javascript"> /*组件插槽:父组件向子组件传递内容 */ Vue.component('alert-box', { template: ` <div> <strong>ERROR:</strong> # 当组件渲染的时候,这个 <slot> 元素将会被替换为“组件标签中嵌套的内容”。 # 插槽内能够蕴含任何模板代码,包含 HTML <slot>默认内容</slot> </div> ` }); var vm = new Vue({ el: '#app', data: { } }); </script></body></html>
5.2 具名插槽
- 具备名字的插槽
- 应用 <slot> 中的 "name" 属性绑定元素
<div id="app"> <base-layout> <!-- 2、组件应用时,有slot属性的标签以及外面的内容会放在slot属性匹配上的插槽地位 如果没有匹配到 则放到匿名的插槽中 --> <p slot='header'>题目信息</p> <p>次要内容1</p> <p>次要内容2</p> <p slot='footer'>底部信息信息</p> </base-layout> <base-layout> <!-- template长期的包裹标签最终不会渲染到页面上,这种形式能够渲染传递多个标签的内容 --> <template slot='header'> <p>题目信息1</p> <p>题目信息2</p> </template> <p>次要内容1</p> <p>次要内容2</p> <template slot='footer'> <p>底部信息信息1</p> <p>底部信息信息2</p> </template> </base-layout> </div> <script type="text/javascript" src="js/vue.js"></script> <script type="text/javascript"> /*具名插槽 */ Vue.component('base-layout', { template: ` <div> <header> ### 1、 应用 <slot> 中的 "name" 属性绑定元素 指定以后插槽的名字 <slot name='header'></slot> </header> <main> <slot></slot> </main> <footer> ### 留神点: ### 具名插槽的渲染程序,齐全取决于模板,而不是取决于父组件中元素的程序 <slot name='footer'></slot> </footer> </div> ` }); var vm = new Vue({ el: '#app', data: { } }); </script></body></html>
5.3 作用域插槽
- 父组件能够对子组件内容加工解决
- 既能够复用子组件的slot,又能够使slot内容不统一
<div id="app"> <!-- 1、当咱们心愿li的款式由内部应用组件的中央定义,因为可能有多种中央要应用该组件, 但款式心愿不一样 这个时候咱们须要应用作用域插槽 --> <fruit-list :list='list'> <!-- 2、父组件中应用了<template>元素,而且蕴含scope="slotProps", slotProps在这里只是长期变量,名称是自定义的 ---> <template slot-scope='slotProps'> <strong v-if='slotProps.info.id==3' class="current"> {{slotProps.info.name}} </strong> <span v-else>{{slotProps.info.name}}</span> </template> </fruit-list> </div> <script type="text/javascript" src="js/vue.js"></script> <script type="text/javascript"> /*作用域插槽*/ Vue.component('fruit-list', { props: ['list'], template: ` <div> <li :key='item.id' v-for='item in list'> ### 3、 在子组件模板中,<slot>元素上有一个相似props传递数据给组件的写法msg="xxx", ### 插槽能够提供一个默认内容,如果如果父组件没有为这个插槽提供了内容,会显示默认的内容。 如果父组件为这个插槽提供了内容,则默认的内容会被替换掉 <slot :info='item'>{{item.name}}</slot> </li> </div> ` }); var vm = new Vue({ el: '#app', data: { list: [{ id: 1, name: 'apple' },{ id: 2, name: 'orange' },{ id: 3, name: 'banana' }] } }); </script></body></html>
六、 基于组件的购物车案例
6.1. 实现组件化布局
- 把动态页面转换成组件化模式
- 把组件渲染到页面上
<div id="app"> <div class="container"> <!-- 2、把组件渲染到页面上 --> <my-cart></my-cart> </div> </div> <script type="text/javascript" src="js/vue.js"></script> <script type="text/javascript"> # 1、 把动态页面转换成组件化模式 # 1.1 题目组件 var CartTitle = { template: ` <div class="title">我的商品</div> ` } # 1.2 商品列表组件 var CartList = { # 留神点 : 组件模板必须是单个根元素 template: ` <div> <div class="item"> <img src="img/a.jpg"/> <div class="name"></div> <div class="change"> <a href="">-</a> <input type="text" class="num" /> <a href="">+</a> </div> <div class="del">×</div> </div> <div class="item"> <img src="img/b.jpg"/> <div class="name"></div> <div class="change"> <a href="">-</a> <input type="text" class="num" /> <a href="">+</a> </div> <div class="del">×</div> </div> <div class="item"> <img src="img/c.jpg"/> <div class="name"></div> <div class="change"> <a href="">-</a> <input type="text" class="num" /> <a href="">+</a> </div> <div class="del">×</div> </div> <div class="item"> <img src="img/d.jpg"/> <div class="name"></div> <div class="change"> <a href="">-</a> <input type="text" class="num" /> <a href="">+</a> </div> <div class="del">×</div> </div> <div class="item"> <img src="img/e.jpg"/> <div class="name"></div> <div class="change"> <a href="">-</a> <input type="text" class="num" /> <a href="">+</a> </div> <div class="del">×</div> </div> </div> ` } # 1.3 商品结算组件 var CartTotal = { template: ` <div class="total"> <span>总价:123</span> <button>结算</button> </div> ` } ## 1.4 定义一个全局组件 my-cart Vue.component('my-cart',{ ## 1.6 引入子组件 template: ` <div class='cart'> <cart-title></cart-title> <cart-list></cart-list> <cart-total></cart-total> </div> `, # 1.5 注册子组件 components: { 'cart-title': CartTitle, 'cart-list': CartList, 'cart-total': CartTotal } }); var vm = new Vue({ el: '#app', data: { } }); </script>
6.2、实现 题目和结算性能组件
题目组件实现动静渲染
- 从父组件把题目数据传递过去 即 父向子组件传值
- 把传递过去的数据渲染到页面上
结算性能组件
- 从父组件把商品列表list 数据传递过去 即 父向子组件传值
- 把传递过去的数据计算最终价格渲染到页面上
<div id="app"> <div class="container"> <my-cart></my-cart> </div> </div> <script type="text/javascript" src="js/vue.js"></script> <script type="text/javascript"> # 2.2 题目组件 子组件通过props模式接管父组件传递过去的uname数据 var CartTitle = { props: ['uname'], template: ` <div class="title">{{uname}}的商品</div> ` } # 2.3 商品结算组件 子组件通过props模式接管父组件传递过去的list数据 var CartTotal = { props: ['list'], template: ` <div class="total"> <span>总价:{{total}}</span> <button>结算</button> </div> `, computed: { # 2.4 计算商品的总价 并渲染到页面上 total: function() { var t = 0; this.list.forEach(item => { t += item.price * item.num; }); return t; } } } Vue.component('my-cart',{ data: function() { return { uname: '张三', list: [{ id: 1, name: 'TCL彩电', price: 1000, num: 1, img: 'img/a.jpg' },{ id: 2, name: '机顶盒', price: 1000, num: 1, img: 'img/b.jpg' },{ id: 3, name: '海尔冰箱', price: 1000, num: 1, img: 'img/c.jpg' },{ id: 4, name: '小米手机', price: 1000, num: 1, img: 'img/d.jpg' },{ id: 5, name: 'PPTV电视', price: 1000, num: 2, img: 'img/e.jpg' }] } }, # 2.1 父组件向子组件以属性传递的模式 传递数据 # 向 题目组件传递 uname 属性 向 商品结算组件传递 list 属性 template: ` <div class='cart'> <cart-title :uname='uname'></cart-title> <cart-list></cart-list> <cart-total :list='list'></cart-total> </div> `, components: { 'cart-title': CartTitle, 'cart-list': CartList, 'cart-total': CartTotal } }); var vm = new Vue({ el: '#app', data: { } }); </script>
6.3. 实现列表组件删除性能
- 从父组件把商品列表list 数据传递过去 即 父向子组件传值
- 把传递过去的数据渲染到页面上
点击删除按钮的时候删除对应的数据
给按钮增加点击事件把须要删除的id传递过去
- 子组件中不举荐操作父组件的数据有可能多个子组件应用父组件的数据 咱们须要把数据传递给父组件让父组件操作数据
- 父组件删除对应的数据
<div id="app"> <div class="container"> <my-cart></my-cart> </div> </div> <script type="text/javascript" src="js/vue.js"></script> <script type="text/javascript"> var CartTitle = { props: ['uname'], template: ` <div class="title">{{uname}}的商品</div> ` } # 3.2 把列表数据动静渲染到页面上 var CartList = { props: ['list'], template: ` <div> <div :key='item.id' v-for='item in list' class="item"> <img :src="item.img"/> <div class="name">{{item.name}}</div> <div class="change"> <a href="">-</a> <input type="text" class="num" /> <a href="">+</a> </div> # 3.3 给按钮增加点击事件把须要删除的id传递过去 <div class="del" @click='del(item.id)'>×</div> </div> </div> `, methods: { del: function(id){ # 3.4 子组件中不举荐操作父组件的数据有可能多个子组件应用父组件的数据 # 咱们须要把数据传递给父组件 让父组件操作数据 this.$emit('cart-del', id); } } } var CartTotal = { props: ['list'], template: ` <div class="total"> <span>总价:{{total}}</span> <button>结算</button> </div> `, computed: { total: function() { // 计算商品的总价 var t = 0; this.list.forEach(item => { t += item.price * item.num; }); return t; } } } Vue.component('my-cart',{ data: function() { return { uname: '张三', list: [{ id: 1, name: 'TCL彩电', price: 1000, num: 1, img: 'img/a.jpg' },{ id: 2, name: '机顶盒', price: 1000, num: 1, img: 'img/b.jpg' },{ id: 3, name: '海尔冰箱', price: 1000, num: 1, img: 'img/c.jpg' },{ id: 4, name: '小米手机', price: 1000, num: 1, img: 'img/d.jpg' },{ id: 5, name: 'PPTV电视', price: 1000, num: 2, img: 'img/e.jpg' }] } }, # 3.1 从父组件把商品列表list 数据传递过去 即 父向子组件传值 template: ` <div class='cart'> <cart-title :uname='uname'></cart-title> # 3.5 父组件通过事件绑定 接管子组件传递过去的数据 <cart-list :list='list' @cart-del='delCart($event)'></cart-list> <cart-total :list='list'></cart-total> </div> `, components: { 'cart-title': CartTitle, 'cart-list': CartList, 'cart-total': CartTotal }, methods: { # 3.6 依据id删除list中对应的数据 delCart: function(id) { // 1、找到id所对应数据的索引 var index = this.list.findIndex(item=>{ return item.id == id; }); // 2、依据索引删除对应数据 this.list.splice(index, 1); } } }); var vm = new Vue({ el: '#app', data: { } }); </script></body></html>
6.4. 实现组件更新数据性能 (上)
- 将输入框中的默认数据动静渲染进去
- 输入框失去焦点的时候 更改商品的数量
- 子组件中不举荐操作数据 把这些数据传递给父组件 让父组件解决这些数据
- 父组件中接管子组件传递过去的数据并解决
<div id="app"> <div class="container"> <my-cart></my-cart> </div> </div> <script type="text/javascript" src="js/vue.js"></script> <script type="text/javascript"> var CartTitle = { props: ['uname'], template: ` <div class="title">{{uname}}的商品</div> ` } var CartList = { props: ['list'], template: ` <div> <div :key='item.id' v-for='item in list' class="item"> <img :src="item.img"/> <div class="name">{{item.name}}</div> <div class="change"> <a href="">-</a> # 1\. 将输入框中的默认数据动静渲染进去 # 2\. 输入框失去焦点的时候 更改商品的数量 须要将以后商品的id 传递过去 <input type="text" class="num" :value='item.num' @blur='changeNum(item.id, $event)'/> <a href="">+</a> </div> <div class="del" @click='del(item.id)'>×</div> </div> </div> `, methods: { changeNum: function(id, event){ # 3 子组件中不举荐操作数据 因为别的组件可能也援用了这些数据 # 把这些数据传递给父组件 让父组件解决这些数据 this.$emit('change-num', { id: id, num: event.target.value }); }, del: function(id){ // 把id传递给父组件 this.$emit('cart-del', id); } } } var CartTotal = { props: ['list'], template: ` <div class="total"> <span>总价:{{total}}</span> <button>结算</button> </div> `, computed: { total: function() { // 计算商品的总价 var t = 0; this.list.forEach(item => { t += item.price * item.num; }); return t; } } } Vue.component('my-cart',{ data: function() { return { uname: '张三', list: [{ id: 1, name: 'TCL彩电', price: 1000, num: 1, img: 'img/a.jpg' }] }, template: ` <div class='cart'> <cart-title :uname='uname'></cart-title> # 4 父组件中接管子组件传递过去的数据 <cart-list :list='list' @change-num='changeNum($event)' @cart-del='delCart($event)'></cart-list> <cart-total :list='list'></cart-total> </div> `, components: { 'cart-title': CartTitle, 'cart-list': CartList, 'cart-total': CartTotal }, methods: { changeNum: function(val) { //4.1 依据子组件传递过去的数据,跟新list中对应的数据 this.list.some(item=>{ if(item.id == val.id) { item.num = val.num; // 终止遍历 return true; } }); }, delCart: function(id) { // 依据id删除list中对应的数据 // 1、找到id所对应数据的索引 var index = this.list.findIndex(item=>{ return item.id == id; }); // 2、依据索引删除对应数据 this.list.splice(index, 1); } } }); var vm = new Vue({ el: '#app', data: { } }); </script>
6.5. 实现组件更新数据性能 (下)
- 子组件通过一个标识符来标记对用的用户点击 + - 或者输入框输出的内容
- 父组件拿到标识符更新对应的组件
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Document</title> <style type="text/css"> .container { } .container .cart { width: 300px; margin: auto; } .container .title { background-color: lightblue; height: 40px; line-height: 40px; text-align: center; /*color: #fff;*/ } .container .total { background-color: #FFCE46; height: 50px; line-height: 50px; text-align: right; } .container .total button { margin: 0 10px; background-color: #DC4C40; height: 35px; width: 80px; border: 0; } .container .total span { color: red; font-weight: bold; } .container .item { height: 55px; line-height: 55px; position: relative; border-top: 1px solid #ADD8E6; } .container .item img { width: 45px; height: 45px; margin: 5px; } .container .item .name { position: absolute; width: 90px; top: 0;left: 55px; font-size: 16px; } .container .item .change { width: 100px; position: absolute; top: 0; right: 50px; } .container .item .change a { font-size: 20px; width: 30px; text-decoration:none; background-color: lightgray; vertical-align: middle; } .container .item .change .num { width: 40px; height: 25px; } .container .item .del { position: absolute; top: 0; right: 0px; width: 40px; text-align: center; font-size: 40px; cursor: pointer; color: red; } .container .item .del:hover { background-color: orange; } </style></head><body> <div id="app"> <div class="container"> <my-cart></my-cart> </div> </div> <script type="text/javascript" src="js/vue.js"></script> <script type="text/javascript"> var CartTitle = { props: ['uname'], template: ` <div class="title">{{uname}}的商品</div> ` } var CartList = { props: ['list'], template: ` <div> <div :key='item.id' v-for='item in list' class="item"> <img :src="item.img"/> <div class="name">{{item.name}}</div> <div class="change"> # 1\. + - 按钮绑定事件 <a href="" @click.prevent='sub(item.id)'>-</a> <input type="text" class="num" :value='item.num' @blur='changeNum(item.id, $event)'/> <a href="" @click.prevent='add(item.id)'>+</a> </div> <div class="del" @click='del(item.id)'>×</div> </div> </div> `, methods: { delCart: function(id) { // 依据id删除list中对应的数据 // 1、找到id所对应数据的索引 var index = this.list.findIndex(item=>{ return item.id == id; }); // 2、依据索引删除对应数据 this.list.splice(index, 1); }, changeNum: function(id, event){ this.$emit('change-num', { id: id, type: 'change', num: event.target.value }); }, sub: function(id){ # 2 数量的减少和缩小通过父组件来计算 每次都是加1 和 减1 不须要传递数量 父组件须要一个类型来判断 是 加一 还是减1 以及是输入框输出的数据 咱们通过type 标识符来标记 不同的操作 this.$emit('change-num', { id: id, type: 'sub' }); }, add: function(id){ # 2 数量的减少和缩小通过父组件来计算 每次都是加1 和 减1 不须要传递数量 父组件须要一个类型来判断 是 加一 还是减1 以及是输入框输出的数据 咱们通过type 标识符来标记 不同的操作 this.$emit('change-num', { id: id, type: 'add' }); }, del: function(id){ // 把id传递给父组件 this.$emit('cart-del', id); } } } var CartTotal = { props: ['list'], template: ` <div class="total"> <span>总价:{{total}}</span> <button>结算</button> </div> `, computed: { total: function() { // 计算商品的总价 var t = 0; this.list.forEach(item => { t += item.price * item.num; }); return t; } } } Vue.component('my-cart',{ data: function() { return { uname: '张三', list: [{ id: 1, name: 'TCL彩电', price: 1000, num: 1, img: 'img/a.jpg' },{ id: 2, name: '机顶盒', price: 1000, num: 1, img: 'img/b.jpg' },{ id: 3, name: '海尔冰箱', price: 1000, num: 1, img: 'img/c.jpg' },{ id: 4, name: '小米手机', price: 1000, num: 1, img: 'img/d.jpg' },{ id: 5, name: 'PPTV电视', price: 1000, num: 2, img: 'img/e.jpg' }] } }, template: ` <div class='cart'> <cart-title :uname='uname'></cart-title> # 3 父组件通过事件监听 接管子组件的数据 <cart-list :list='list' @change-num='changeNum($event)' @cart-del='delCart($event)'></cart-list> <cart-total :list='list'></cart-total> </div> `, components: { 'cart-title': CartTitle, 'cart-list': CartList, 'cart-total': CartTotal }, methods: { changeNum: function(val) { #4 分为三种状况:输入框变更、加号变更、减号变更 if(val.type=='change') { // 依据子组件传递过去的数据,跟新list中对应的数据 this.list.some(item=>{ if(item.id == val.id) { item.num = val.num; // 终止遍历 return true; } }); }else if(val.type=='sub'){ // 减一操作 this.list.some(item=>{ if(item.id == val.id) { item.num -= 1; // 终止遍历 return true; } }); }else if(val.type=='add'){ // 加一操作 this.list.some(item=>{ if(item.id == val.id) { item.num += 1; // 终止遍历 return true; } }); } } } }); var vm = new Vue({ el: '#app', data: { } }); </script></body></html>