vue组件

vue组件

目录

◽ 组件使用中的细节
◽ 父子组件的数据传递
◽ 组件参数校验与非props特性
◽ 给组件绑定原生事件
◽ 非父子组件间的传值(bus/总线/发布订阅模式/观察者模式)
◽ 在vue中使用插槽
◽ vue中的作用域插槽
◽ 动态组件与v-once指令

一、组件使用中的细节

1.1

1
2
3
4
5
6
7
8
9
<div id="app">
<table>
<tbody>
<row></row>
<row></row>
<row></row>
</tbody>
</table>
</div>
1
2
3
4
5
6
Vue.component('row',{
template:'<tr><td>this is a row</td></tr>'
});
var vm = new Vue({
el:'#app',
})

  在页面中可以把内容展示出来,但是在控制台中:

1
2
3
4
5
6
7
8
<div id="app">
<tr><td>this is a row</td></tr>
<tr><td>this is a row</td></tr>
<tr><td>this is a row</td></tr>
<table>
<tbody></tbody>
</table>
</div>

  并没有在table标签里面反而跑到了外面,因为在h5的规范里面要求table里面是tbody,tbody里面必须放tr。而我们写成了子组件row,所以浏览器解析的时候就会出问题。
解决方案:
  使用vue的is属性(虽然这里写的是tr,但是实际上是row,这样既能保证里面写的是我们的组件,又能保证符合h5的编码规范)

1
<tr is="row"></tr>

1.2

1
2
3
4
5
6
7
8
9
<div id="app">
<table>
<tbody>
<tr is="row"></tr>
<tr is="row"></tr>
<tr is="row"></tr>
</tbody>
</table>
</div>
1
2
3
4
5
6
7
8
9
10
11
Vue.component('row',{
data:function () {
return {
content:'this is a row'
}
},
template:'<tr><td>{{ content }}</td></tr>'
});
var vm = new Vue({
el:'#app',
})

  在子组件定义data必须是个函数而不能是一个对象。之所以这么设计,因为一个子组件不像根组件只会被调用一次。每一个子组件它的数据不希望和其他子组件产生冲突,通过一个函数来返回一个对象的目的是让每一个子组件都拥有一个独立的数据存储。避免多个子组件之间互相影响。

1.3 ref引用

  此demo效果是实现数字计数,第三个数字是前两个数字相加的和。

1
2
3
4
5
<div id="app">
<counter ref="one" @change="changeHandel"></counter>
<counter ref="two" @change="changeHandel"></counter>
<div>{{ total }}</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
24
25
Vue.component('counter',{
template:"<div @click='clickHandel'>{{ number }}</div>",
data:function () {
return{
number:0
}
},
methods:{
clickHandel:function () {
this.number++;
this.$emit('change'); //每次触发clickHandel,$emit就是子组件会给父组件一个信号,去触发changeHandel事件
}
}
});
var vm = new Vue({
el:'#app',
data:{
total:''
},
methods: {
changeHandel:function () {
this.total = this.$refs.one.number + this.$refs.two.number; //this.$refs.名字获取到的是标签对应的dom元素
}
}
})

二、父子组件的数据传递

1
2
3
4
5
<div id="root">
<!--父组件向子组件传递了一个叫count的数据-->
<counter :count="0"></counter>
<counter :count="1"></counter>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var counter = {     //局部组件
props: ['count'], //子组件接受count的格式
template: '<div @click="clickHandel">{{count}}</div>',
methods:{
clickHandel:function () {
this.count ++
}
}
};
var vm = new Vue({
el:"#root",
components:{ //需要注册一下局部组件
counter:counter
}
})

  当点击实现累加时候控制台会出现警告:Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "count"

  那是因为改变了父组件传过来的数据。在vue中有一个单向数据流的概念:父组件可以向子组件传递参数,通过属性的形式传,传递的参数可以随便修改;但是子组件绝对不能反过来去修改父组件传递过来的这个参数。

  解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var counter = {     //局部组件
props: ['count'], //子组件接受count的格式
data:function(){
return{
number: this.count
}
},
template: '<div @click="clickHandel">{{number}}</div>',
methods:{
clickHandel:function () {
this.number ++
}
}
};

改进:

1
2
3
4
5
6
7
<div id="root">
<!--父组件向子组件传递了一个叫count的数据-->
<!--@change监听子组件传过来的数据-->
<counter :count="0" @change="changeHandel"></counter>
<counter :count="1" @change="changeHandel"></counter>
<div> {{ total }} </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
24
25
26
27
28
29
var counter = {     //局部组件
props: ['count'], //子组件接受count的格式
data:function(){
return{
number: this.count
}
},
template: '<div @click="clickHandel">{{number}}</div>',
methods:{
clickHandel:function () {
this.number ++;
this.$emit('change',1); //1传给父组件
}
}
};
var vm = new Vue({
el:"#root",
components:{ //需要注册一下局部组件
counter:counter
},
data:{
total:5
},
methods: {
changeHandel:function (step) { //step接收$emit里面的参数
this.total+=step
}
}
})

三、组件参数校验与非props特性

3.1 组件参数校验

1
2
3
<div id="root">
<child content="hello jin"></child>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Vue.component('child',{
// props:['content'],
props:{
content:{
type: String, //这种格式写法父组件传给子组件的参数给了约束,必须要以字符串形式
required: false, //为true时content是必传的,如果为false,可传可不传,相当于required不写
default: '这是默认值', //当content不传时,default为默认值
}
},
template: "<div>{{ content }}</div>"
});
var vm = new Vue({
el: '#root',
})
1
2
3
4
5
6
7
8
9
10
content:{
type: String, //这种格式写法父组件传给子组件的参数给了约束,必须要以字符串形式
required: false, //为true时content是必传的,如果为false,可传可不传,相当于required不写
default: '这是默认值', //当content不传时,default为默认值

//对传过来的content进行校验,如果它的长度大于5,返回true;小于5,返回false,报错
validator: function (value) {
return (value.length > 5)
}
}

3.2 非props特性

  当父组件向子组件传递了一个属性,但是子组件并没有声明props这块内容(没有声明父组件到传递过来的内容),这个时候会报错。实际开发当中非props特性用的并不是很多。

1
2
3
<div id="root">
<child content="hello jin"></child>
</div>

1
2
3
4
5
6
Vue.component('child',{
template: "<div>{{ content }}</div>"
});
var vm = new Vue({
el: '#root',
})

  如果这个时候template里面不是写差值表达式而是写死,那么在控制台里面的html结构里面<div content="hello jin">你好</div>,属性是会展示在子组件的标签里面

1
2
3
<div id="root">
<child content="hello jin"></child>
</div>

1
2
3
4
5
6
Vue.component('child',{
template: "<div>你好</div>"
});
var vm = new Vue({
el: '#root',
})

四、给组件绑定原生事件

1
2
3
<div id="root">
<child @click="clickHandel"></child>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Vue.component('child',{
template: "<div @click='clickChildHandel'>child</div>",
methods: {
clickChildHandel:function () {
this.$emit('click')
}
}
});
var vm = new Vue({
el: '#root',
methods:{
clickHandel:function () {
alert(123)
}
}
})

  这样包裹两层太麻烦了,想要直接监听原生事件可以用native修饰符:

1
2
3
<div id="root">
<child @click.native="clickHandel"></child>
</div>

1
2
3
4
5
6
7
8
9
10
11
Vue.component('child',{
template: "<div>child</div>",
});
var vm = new Vue({
el: '#root',
methods:{
clickHandel:function () {
alert(123)
}
}
})

五、非父子组件间的传值(bus/总线/发布订阅模式/观察者模式)

1
2
3
4
5
<div id="root">
<!--这两个child不是父子组件的关系,是兄弟间的关系-->
<child content="will"></child>
<child content="jin"></child>
</div>
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
Vue.prototype.bus = new Vue();

Vue.component('child',{
data:function(){
return {
selfContent: this.content
}
},
props: {
content: String
},
template: '<div @click="clickHandel">{{ selfContent }}</div>',
methods:{
clickHandel:function () {
this.bus.$emit('change',this.selfContent)
}
},
mounted:function () {
var this_ = this;
this.bus.$on('change',function (msg) {
this_.selfContent = msg;
})
}
});
var vm = new Vue({
el: '#root'
})

六、在vue中使用插槽

  通过插槽我们可以方便的向子组件传递dom元素,同时子组件使用插槽slot就可以了。

1
2
3
4
5
<div id="root">
<child>
<p>jin</p>
</child>
</div>

1
2
3
4
5
6
7
8
9
Vue.component('child',{
template: `<div>
<p>你好</p>
<slot>默认内容</slot>
</div>`
});
var vm = new Vue({
el: "#root"
})

  插槽有一个,具名插槽可以有多个

1
2
3
4
5
6
<div id="root">
<child>
<div class="header" slot="header">header</div>
<div class="footer" slot="footer">footer</div>
</child>
</div>

1
2
3
4
5
6
7
8
9
10
Vue.component('child',{
template: `<div>
<slot name="header">默认内容</slot>
<p>你好</p>
<slot name="footer">默认内容</slot>
</div>`
});
var vm = new Vue({
el: "#root"
})

展示效果:
header
你好
footer

七、vue中的作用域插槽

  当子组件循环或者某一部分dom结构应该由外部传递进来的时候需要用到作用域插槽。
作用域插槽必须是在template标签里面的,且从子组件接受的数据都放在slot-scope里。当子组件用slot的时候,slot传递一个item这样的一个数据,数据放在了slot-scope的jin里面(可以自定义取名)。

1
2
3
4
5
6
7
<div id="root">
<child>
<template slot-scope="jin">
<li>{{jin.item}}</li>
</template>
</child>
</div>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Vue.component('child',{
data:function(){
return{
list:[1, 2, 3, 4]
}
},
template: `<div>
<ul>
<slot v-for="item of list" :item="item"></slot>
</ul>
</div>`
});
var vm = new Vue({
el:'#root'
})

八、动态组件与v-once指令

点击按钮实现child-one和child-two的toggle效果

1
2
3
4
5
<div id="root">
<child-one v-if="type === 'child-one'"></child-one>
<child-two v-if="type === 'child-two'"></child-two>
<button @click="clickHandel">change</button>
</div>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Vue.component('child-one',{
template:'<div>child-one</div>'
});
Vue.component('child-two',{
template:'<div>child-two</div>'
});
var vm = new Vue({
el: "#root",
data:{
type: 'child-one'
},
methods:{
clickHandel:function () {
this.type = this.type === 'child-one' ? 'child-two':'child-one'
}
}
})

动态组件的方式:
  component标签是vue里面自带的标签,它指的就是一个动态组件。它的is属性绑定type,type为child-one,他就展示child-one,销毁掉child-two这个组件;而每次切换的时候,底层都会销毁一个组件,这种操作耗费了一定的性能。这个时候用v-once,当child-one第一次被渲染的时候,它就会被放到内存里面,当切换时child-two被放进内存,这样就直接从内存里拿出来就可以了。所以v-once指令可有效提高一些静态内容的展示效率。

1
2
3
4
<div id="root">
<component :is="type"></component>
<button @click="clickHandel">change</button>
</div>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Vue.component('child-one',{
template:'<div v-once>child-one</div>'
});
Vue.component('child-two',{
template:'<div v-once>child-two</div>'
});
var vm = new Vue({
el: "#root",
data:{
type: 'child-one'
},
methods:{
clickHandel:function () {
this.type = this.type === 'child-one' ? 'child-two':'child-one'
}
}
})
# VUE
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×