组件注册
1 2 3 4
| Vue.component(组件名称,{ data:组件数据, template:组件模板内容 })
|
简单用法
1 2 3 4 5 6 7 8 9 10 11 12 13
| Vue.component('button-counter', { data: function () { return { count: 0 } }, template: '<button @click="add">点击了{{count}}次</button>', methods: { add() { this.count++ } }, })
|
1 2 3
| <div id="app"> <button-counter><button-counter> </div>
|
组件可以多次复用,并且每个组件之间互不影响。
组件注意事项
定义组件时data
属性为一个函数,函数内返回一个对象。这个对象中包含数据
1 2 3 4 5
| data: function () { return { count: 0 } }
|
组件模板内容必须是单个根元素
1
| template: '<button @click="add">点击了{{count}}次<p>p标签</p></button>',
|
组件模板内容可以是模板字符串
组件命名方式
局部组件注册方式
1 2 3 4 5 6 7
| var ComponentA = {} var vm = new Vue({ el: '#app', components: { 'component-a': ComponentA } });
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| <body> <div id="app"> <component-a></component-a> <component-b></component-b> <component-c></component-c> </div> <script type="text/javascript" src="../js/vue.js"></script> <script type="text/javascript"> var ComponentA = { data: function () { return { msg: 'HelloWorld' } }, template: '<div>{{msg}}</div>' } var ComponentB = { data: function () { return { msg: 'HelloTOM' } }, template: '<div>{{msg}}</div>' } var ComponentC = { data: function () { return { msg: 'HelloVue' } }, template: '<div>{{msg}}</div>' } var vm = new Vue({ el: '#app', components: { 'component-a': ComponentA, 'component-b': ComponentB, 'component-c': ComponentC }
}); </script> </body>
|
Vue调试工具
可以直接使用谷歌商城进行安装:https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd/related?hl=zh
也可以参考官方自行构建后安装:https://github.com/vuejs/vue-devtools
如果遇到Vue.js not detected
问题可能导致的原因:
没有开启权限
组件间的交互
父组件向子组件传值
在组建中定义props
属性,值为一个数组,用于接受来自父组件的值
1 2 3 4 5 6 7 8 9
| Vue.component('menu-item', { props: ['title'], data: function () { return { msg: '子组件' } }, template: '<div>{{msg}}---{{title}}</div>' })
|
在使用时可以有两种写法,
静态传入
1
| <menu-item title='来自父组件的值'></menu-item>
|
通过属性绑定
1
| <menu-item :title='ptitle'></menu-item>
|
1 2 3 4 5 6 7
| var vm = new Vue({ el: '#app', data: { pmsg: "父组件", ptitle: '来自父组件的值' } });
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <body> <div id="app"> <menu-item title='来自父组件的值'></menu-item> <menu-item :title='ptitle'></menu-item> </div> <script type="text/javascript" src="../js/vue.js"></script> <script type="text/javascript"> Vue.component('menu-item', { props: ['title'], data: function () { return { msg: '子组件' } }, template: '<div>{{msg}}---{{title}}</div>' }) var vm = new Vue({ el: '#app', data: { pmsg: "父组件", ptitle: '来自父组件的值' } }); </script> </body>
|
props属性名
规则
在props
使用驼峰式传值,那么模板中需要使用短横线的形式
1 2 3 4
| Vue.component('menu-item', { props: ['TitleMsg'], template: '<div>{{TitleMsg}}</div>' })
|
1
| <menu-item title-msg='nihao'></menu-item>
|
在HTML中大小写是不敏感的。
字符串模板中没有这个限制
1 2 3 4
| Vue.component('menu-item', { props: ['TitleMsg'], template: '<div><menu-item TitleMsg="nihao"></menu-item></div>' })
|
属性值
以下类型均可以传递
字符串
数值类型
如果通过v-bind
绑定,那么数值为数字类型
布尔类型
如果通过v-bind
绑定,那么数值为布尔类型
数组类型
对象
子组件向父组件传值
子组件通过自定义事件向父组件传递信息
1
| <menu-item :parr='parr' @enlarge-text='handle'></menu-item>
|
自定义事件名为enlarge-text
,触发的函数名为handle
1 2 3 4 5 6 7 8 9 10 11
| Vue.component('menu-item', { props: ['parr'], template: ` <div> <ul> <li :key='index' v-for='(item,index) in parr'>{{item}}</li> </ul> <button @click='$emit("enlarge-text")'>扩大父组件中字体大小</button> </div> ` });
|
对按钮进行单击事件绑定,绑定的事件为enlarge-text
。$emit
为固定用法。
父组件通过监听子组件的事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| var vm = new Vue({ el: '#app', data: { pmsg: '父组件中内容', parr: ['apple', 'orange', 'banana'], fontSize: 10 }, methods: { handle: function () { this.fontSize += 5; } } });
|
子组件向父组件传值-携带参数
定义组件时可以通过$emit
的第二个参数进行传值。组件调用时通过$event
来接受。
使用组件
1
| <menu-item :parr='parr' @enlarge-text='handle($event)'></menu-item>
|
定义组件
1 2 3 4 5 6 7 8 9 10 11
| Vue.component('menu-item', { props: ['parr'], template: ` <div> <ul> <li :key='index' v-for='(item,index) in parr'>{{item}}</li> </ul> <button @click='$emit("enlarge-text",5)'>扩大父组件中字体大小</button> </div> ` });
|
父组件接受值
1 2 3 4 5 6
| methods: { handle: function (val) { this.fontSize += val; } }
|
非父子组件间传值
单独的事件中心管理组件间的通信
监听事件与销毁事件
1 2 3
| hub.$on('tom-event', (val) => { this.num += val })
|
触发事件
1
| hub.$emit('jerry-event', 1)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| <body> <div id="app"> <test-tom></test-tom> <test-jerry></test-jerry> </div> <script type="text/javascript" src="../js/vue.js"></script> <script type="text/javascript"> 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 () { hub.$emit('jerry-event', 1) } }, mounted: function () { 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 () { hub.$emit('tom-event', 2) } }, mounted: function () { hub.$on('jerry-event', (val) => { this.num += val }) }, }); var vm = new Vue({ el: '#app', }); </script> </body>
|
组件插槽
父组件向子组件传递内容(模板内容)
定义插槽
1 2 3 4 5 6 7 8
| Vue.component('alert-box', { template: ` <div> <strong>Error:<strong> <slot>默认内容</slot> </div> ` })
|
定义插槽使用slot
标签,如果没有传值,那么显示定义时的默认内容。
使用插槽
1 2
| <alert-box>有bug发生</alert-box> <alert-box></alert-box>
|
当传递值时显示值的内容,否则显示默认内容(如果有)
具名插槽
即对slot
标签添加name
属性,表示当前插槽的名称。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Vue.component('base-layout', { template: ` <div> <header> <slot name='header'></slot> </header> <main> <slot></slot> </main> <footer> <slot name='footer'></slot> </footer> </div> ` })
|
调用时,通过slot
属性指定填充插槽名。没有指定的则添加到默认插槽
1 2 3 4 5
| <base-layout> <p slot="header">标题</p> <p>主要内容</p> <p slot="footer">尾部</p> </base-layout>
|
关于调用也可以使用如下方法
1 2 3 4 5 6 7 8 9 10 11
| <base-layout> <template slot="header"> <p slot="header">标题</p> </template> <template> <p>主要内容</p> </template> <template slot="footer"> <p slot="footer">尾部</p> </template> </base-layout>
|
作用域插槽
父组件对子组件的内容进行加工处理。
插槽定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| Vue.component('fruit-list', { props: ['list'], template: ` <div> <li :key='item.id' v-for='item in list'> <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' }] } });
|
插槽内容
1 2 3 4 5 6
| <fruit-list :list='list'> <template v-slot='slotProps'> <strong v-if='slotProps.info.id==3' class="current">{{slotProps.info.name}}</strong> <span v-else>{{slotProps.info.name}}</span> </template> </fruit-list>
|
值的传递:
插槽中定义了属性info
为item
(每次遍历的结果)
使用v-slot
指令接受,其值可以为任意喜欢的名字。
1
| <template v-slot='slotProps'></template>
|
其slotProps
实际上就是info
的值。
1 2 3 4 5 6 7 8
| <fruit-list :list='list'> <template v-slot='slotProps'>
<strong v-if='slotProps.info.id==3' class="current">{{slotProps.info.name}}</strong> <span v-else>{{slotProps.info.name}}</span> <span>{{slotProps}}</span> </template> </fruit-list>
|
购物车案例
组件化重构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
| <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 = { template: ` <div class="title">我的商品</div> ` } 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> ` } var CartTotal = { template: ` <div class="total"> <span>总价:123</span> <button>结算</button> </div> ` } Vue.component('my-cart', { template: ` <div class='cart'> <cart-title></cart-title> <cart-list></cart-list> <cart-total></cart-total> </div> `, components: { 'cart-title': CartTitle, 'cart-list': CartList, 'cart-total': CartTotal } }); var vm = new Vue({ el: '#app', data: {
} });
</script> </body>
|
标题和总价
标题的实现很简单,为了演示功能,定义一个属性记录用户名,并传递给header
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| Vue.component('my-cart', { data: function () { return { uname: '张三', } }, template: ` <div class='cart'> <cart-title :uname='uname'></cart-title> <cart-list></cart-list> <cart-total></cart-total> </div> `, }
|
将值显示出来
1 2 3 4 5 6
| var CartTitle = { props: ['uname'], template: ` <div class="title">{{uname}}的商品</div> ` }
|
对于总价,提供一个数据用于记录购物车的列表。并将值传递给total
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| 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> <cart-list></cart-list> <cart-total :list='list></cart-total> </div> `, }
|
在CartTotal组件中根据列表中的内容计算总价。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| var CartTotal = { props: ['list'], template: ` <div class="total"> <span>总价:{{total}}</span> <button>结算</button> </div> `, computed: { total: function () { var sum = 0 console.log(this.list); this.list.forEach(item => { sum += item.price * item.num }) return sum } } }
|
列表展示和删除
列表展示只需要在父组件中将数组列表传递给子组件,子组件在进行循环遍历即可。
1 2 3 4 5 6 7 8 9 10
| Vue.component('my-cart', { template: ` <div class='cart'> <cart-title :uname='uname'></cart-title> <cart-list :list='list'></cart-list> <cart-total :list='list'></cart-total> </div> `, } )
|
子组件中循环遍历数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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> <div class="del"'>×</div> </div> </div> ` }
|
对于删除操作来说,由于数据来源于父组件,所以不建议直接删除,而是通过自定义事件通知父级。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| 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> <div class="del" @click='del(item.id)'>×</div> </div> </div> `, methods: { del: function (id) { this.$emit('cart-del', id) } }, }
|
父级中定义删除的实际操作,并监听事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| Vue.component('my-cart', { methods: { delCart: function (id) { var index = this.list.findIndex(item => { return item.id == id }) this.list.splice(index, 1) } }, template: ` <div class='cart'> <cart-title :uname='uname'></cart-title> <cart-list :list='list' @cart-del='delCart($event)'></cart-list> <cart-total :list='list'></cart-total> </div> `, });
|
商品数量的变更
商品数量变更同样涉及到修改列表内容,同样的交给父组件去修改。
在子组件中定义自定义事件并对input
标签绑定失去焦点时触发这个自定义事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| 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" :value='item.num' @blur='changeNum(item.id,$event)'/> <a href="">+</a> </div> <div class="del" @click='del(item.id)'>×</div> </div> </div> `, methods: { del: function (id) { this.$emit('cart-del', id) }, changeNum: function (id, event) { let value = event.target.value this.$emit('change-num', { id: id, num: value }) } }, }
|
自定义事件中,将商品id和修改的值传递给父组件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| Vue.component('my-cart', { template: ` <div class='cart'> <cart-title :uname='uname'></cart-title> <cart-list :list='list' @change-num='changeNum($event)' @cart-del='delCart($event)'></cart-list> <cart-total :list='list'></cart-total> </div> `, methods: { changeNum: function (val) { console.log(val); this.list.some(item => { if (item.id == val.id) { item.num = val.num return true } }) } }, });
|
父组件监听子组件定义的自定义方法,并触发父组件定义的自定义方法。
按钮操作
按钮操作同样是需要利用父组件来修改数据。为了复用changeNum
事件,将其在添加一个值type
用于记录当前的操作。子组件修改。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| changeNum: function (id, event) { let value = event.target.value this.$emit('change-num', { id: id, num: value, type: 'change' }) }, sub: function (id) { this.$emit('change-num', { id: id, type: 'sub' }) }, add: function (id) { this.$emit('change-num', { id: id, type: 'add' }) },
|
父组件的修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| changeNum: function (val) { if (val.type == 'change') { 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 { this.list.some(item => { if (item.id == val.id) { item.num += 1 return true } }) }
|