组件化开发
组件化开发思想
- 标准
- 分治
- 重用
- 组合
组件开发规范:Web Components
通过封装好功能的定制元素(自定义标签)来解决问题
Vue部分实现来上述规范
组件的注册方式
Vue.component(组件名称,{
data:组件数据,
template:组件模板内容
})
一个具体组件:
Vue.component('button-counter',{
data:function(){
return{
count:0
}
},
template:'<button @click="count++" >按钮被点击{{count}}次</button>'
})
组件是可以重用的,重用的每个组件是相互独立的,他们之间的数据是相互不影响的
注意事项:
1 自定义组件的data必须是一个函数,而Vue实例的data可以是个对象
2 组件内部的模板根元素只应该有一个
3 组件模板内容可以是模板字符串
eg:
template:`
<button @click='count++'>
按钮被点击{{count}}次
</button>
`
4 组件的命名方式有短横线方式和驼峰命名方式
如果组件要在别的组件中使用,可以直接按照命名的写法用
如果要在html中直接使用,在命名时无论用的哪种方法,使用时都得时短横线方式
- 局部组件
//注册一个Vue实例中的局部组件
var ComponentA = {
data:function(){
return {
}
},
template:``
}
var vm = new Vue({
el:'#box',
components:{
'component-a':ComponentA
}
})
组件之间数据的交互
-
父组件向子组件传值
1 组件内部通过props接收传递过来的值
2 父组件将值通过v-bind绑定到属性上,用以传给子组件
image.png
props属性名规则:
1 在props中使用驼峰形式,在模板中需要使用短横线模式
2 字符串形式的模版中没有这个限制
props属性值类型:
- 字符串
- 数值
- 布尔
- 数组
- 对象
在传递数字的时候,如果用了v-bind,传过去的是数值,否则就是string
传递布尔型的值时,属性可以不加引号,但是推荐加上,而且也需要v-bind绑定 - 子组件向父组件传值
props传递数据的原则:单向数据流,只能父传子,不能子传父
-- 子组件通过自定义事件的方式向父组件传值
举个例子:
业务场景:点击子组件的按钮,增大父组件中的值的大小(子组件向父组件传递信息,没有传值)
<body>
<div id="box">
<div :style='{fontSize:fontSize+"px"}'>{{msg}}</div>
<button-son @enlarge-text='handle'></button-son>
</div>
<script>
Vue.component('button-son',{
template:`
<button @click='$emit("enlarge-text")'>扩大父组件的字体大小</button>
`
})
var vm = new Vue({
el:'#box',
data() {
return {
msg:'Hello world',
fontSize:13
}
},
methods: {
handle:function(){
//扩大字体大小
this.fontSize ++;
console.log(this.fontSize);
}
},
})
</script>
</body>
-- 传值:在emit时传递的第一个参数是自定义事件名,而第二个参数则可以用来传递值,在父组件中用$event来接收值
子组件:
<button @click='$emit("enlarge-text",2)'>扩大父组件的字体大小</button>
父组件:
<button-son @enlarge-text='handle($event)'></button-son>
- 非父子组件之间传值:
-- 单独的事件中心管理组件间的通信
var eventBus = new Vue();
-- 监听事件与销毁事件
eventBus.$on('add-todo',addtodo);
eventBus.$off('add-todo')
-- 触发事件
eventBus.$emit('add-todo',id)
image.png
实例:在一个父组件中有两个子组件,都是button和data,点击第一个子组件的button,第二个子组件的data改变,点击第二个子组件的button,第一个子组件的data改变。
<body>
<div id="box">
<p>father</p>
<button v-on:click='handle'>销毁事件</button>
<button-a></button-a>
<button-b></button-b>
</div>
<script>
//事件中心
var eventBus = new Vue()
Vue.component('button-a',{
data:function(){
return {
msg:0
}
},
template:`
<div>
<div>A:{{msg}}</div>
<button @click='handle'>给老弟加2</button>
</div>
`,
methods: {
handle:function(){
eventBus.$emit('b-event',2)
}
},
mounted() {
//监听事件
eventBus.$on('a-event',(val)=>{
this.msg += val
})
},
})
Vue.component('button-b',{
data:function(){
return {
msg:0
}
},
template:`
<div>
<div>B:{{msg}}</div>
<button @click='handle'>给老哥加3</button>
</div>
`,
methods: {
handle:function(){
//触发兄弟组件的事件
eventBus.$emit('a-event',3)
}
},
mounted() {
//监听事件
eventBus.$on('b-event',(val)=>{
this.msg += val
})
},
})
var vm = new Vue({
el:'#box',
data:{
},
methods: {
handle:function(){
eventBus.$off('a-event')
eventBus.$off('b-event')
}
},
})
</script>
</body>
组件插槽的用法
在之前的自定义组件中,我们在使用的时候都是没有具体值的,比如:
<a-button><a-button>
可如果我们想要这样使用呢?:
<a-button>click me</a-button>
这时候,我们要使用插槽
我们在模板字符串中定义组件的dom时,可以预留<slot></slot>,
在使用组件时输入的值就在slot中
eg:
<my-com>I don't know...</my-com>
...
Vue.component('my-com',{
template:`
<div>
<strong>Error : </strong>
<slot></slot>
</div>
`
})
-- 具名插槽
定义方法:
template:`
<div>
<div>
<slot name='header'></slot>
</div>
<strong>Error : </strong>
<div><slot></slot></div>
<div>
<slot name='footer'></slot>
</div>
</div>
`
使用:
<my-com>
<div slot="header">标题信息</div>
I don't know...
<div slot="footer">底部信息</div>
</my-com>
给了name的插槽对号入座,没有给name的,放入默认插槽中
-- 作用域插槽:
应用场景:父组件可以对子组件的内容进行加工处理
例子:子组件是个水果列表,但是高亮内容和文本内容应该是动态的
<fruit-list v-bind:list='list'>
<template slot-scope="slotProps">
<strong v-if='slotProps.info.id==2' class="orange">{{slotProps.info.name}}</strong>
<span v-else>{{slotProps.info.name}}</span>
</template>
</fruit-list>
//组件定义
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>
`
})
Vue组件调试工具的用法
组件在浏览器中会被渲染成原始的DOM,我们在浏览器的检查工具上只能看到已经渲染好了的DOM,不便于调试
官方调试工具,dev-tools,直接下载安装chrome插件即可
https://github.com/vuejs/vue-devtools
基于组件方式实现业务功能
一个小例子:
<body>
<script>
/*
1 组件化划分「
1.标题组件(展示文本)
2.列表组件(列表展示、商品数量变更、商品删除)
3.结算组件(计算商品总额)
」
*/
</script>
<div id="box">
<div id="containner">
<my-cart></my-cart>
</div>
</div>
<script>
var cartTitle = {
props:['uname'],
template:`
<div class='title'>{{uname}}的商品</div>
`
}
var list = {
props:['list'],
template:`
<div>
<div :key=item.id v-for='item in list'>
<img :src="item.img" alt="">
<div>{{item.name}}</div>
<div class='change'>
<a class='a' href="" @click.prevent='sub(item.id)'>-</a>
<input type="text" class='num' :value='item.num' @blur='changeNum(item.id,$event)'/>
<a class='a' href="" @click.prevent='add(item.id)'>+</a>
<button class='delete' @click='del(item.id)'>✖️</button>
</div>
</div>
</div>
`,
methods: {
changeNum:function(id,event){
this.$emit('numChange',{id:id,num:parseInt( event.target.value),type:'change'})
//console.log(id,parseInt( event.target.value));
//新的值是event.target.value
},
del:function(id){
this.$emit('cart-del',id)
},
sub:function(id){
this.$emit('numChange',{
id:id,
type:'sub'
})
},
add:function(id){
this.$emit('numChange',{
id:id,
type:'add'
})
}
},
}
var totol = {
props:['list'],
template:`
<div class='totol'>
<span>{{result}}</span>
<button>结算</button>
</div>
`,
computed: {
result:function(){
var totolNum = 0;
for(var i = 0;i<this.list.length;i++){
totolNum += this.list[i].num * this.list[i].price;
}
// console.log(this.list.length);
// console.log(totolNum);
return totolNum;
}
}
}
Vue.component('my-cart',{
data:function(){
return{
list:[
{id:1,name:'tcl',img:'',num:1,price:100},
{id:2,name:'tl',img:'',num:1,price:1100},
{id:3,name:'tfel',img:'',num:1,price:1200},
{id:4,name:'gdgmkll',img:'',num:1,price:1400},
{id:5,name:'fghl',img:'',num:2,price:1900}
],
uname:'sam'
}
},
template:`
<div class='cart'>
<cart-title :uname='uname'></cart-title>
<cart-list :list='list' @numChange='changeNum($event)' @cart-del='delCart($event)'></cart-list>
<cart-totol :list='list'></cart-totol>
</div>
`,
components:{
'cart-title':cartTitle,
'cart-list':list,
'cart-totol':totol
},
methods:{
delCart:function(id){
console.log(id);
//根据ID删除list中对应的数据
//1 根据id找到需要删除的数据的索引
var index = this.list.findIndex(item=>{
return item.id == id
})
//2 根据索引删除对应的数据
this.list.splice(index,1)
},
changeNum:function(obj){
//根据子组件传递来的数据,更新list的数据
// for(var i = 0; i< this.list.length;i++){
// if(this.list[i].id == obj.id){
// this.list[i].num = obj.num
// }
// }
if(obj.type == 'change'){
this.list.some(item=>{
if(item.id == obj.id){
item.num = obj.num
return true
}
})
//some这个API用起来遍历,简洁了很多
}else if(obj.type == 'sub'){
this.list.some(item=>{
if(item.id == obj.id&&item.num!=1){
item.num -= 1;
return true
}
})
}
else{
this.list.some(item=>{
if(item.id == obj.id){
item.num += 1
return true
}
})
}
}
}
})
var vm = new Vue({
el:'#box',
data:{
},
})
</script>
</body>