秋招复习计划-Vue知识点4

  |  

前言

深入理解Vue中的组件使用


56


组件的基本使用

注册组件

全局注册

使用Vue.component()方法,先传入要给自定义组件的名字,然后传入这个组件的配置。

全局注册的组件可以在任何新建的Vue根实例的模板中使用

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
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Component</title>
<script src="./vue.js"></script>
</head>
<body>
<div id='app'>
<mycomponent></mycomponent>
</div>
<script>
Vue.component('mycomponent', {
template: '<div>这是一个{{ message }}组件</div>',
data() {
return {
message: 'hello world'
}
}
})
let app = new Vue({
el: '#app'
})

</script>
</body>
</html>

局部组件

除了使用Vue.component()注册全局组件,我们还可以在某个Vue实例中注册只有自己能使用的组件

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
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Component</title>
<script src="./vue.js"></script>
</head>
<body>
<div id='app'>
<my-component></my-component>
</div>
<script>
let app = new Vue({
el: '#app',
data: {},
components: {
'my-component' : {
template: '<div>这是一个{{ message }}组件</div>',
data() {
return {
message: '局部'
}
}
}
}
})
</script>
</body>
</html>

模板的要求

注意:组件的模板只能有一个根元素。下面这种情况是不允许的

1
2
template: '<div>这是一个模板</div>
<button>click</button>'

组件中的data必须是函数

可以看出注册组件与创建Vue实例差不多,但是,组件的data属性必须是要给函数。

如果我们像Vue实例一样,传入一个对象,因为对象是引用类型,所以当存在多个这样的组件时,就会共享数据,导致一个组件中数据的改变会引起其它组件数据的改变。

而是用一个返回对象的函数,每次使用组件都会创建一个新的对象,这样就能避免数据共享的问题。

DOM模板解析的限制

当使用DOM作为模板时,会受到HTML 的一些限制,因为Vue只有在浏览器解析和标准化HTML后才能获取模板内容。尤其像<ul><ol><table><select>限制能被他们包裹的元素。

在自定义组件中使用这些受限制的元素时会导致一些问题,例如:

1
2
3
<table>
<my-row>...</my-row>
<table>

自定义组件<my-row>将被认为是无效内容,因此在渲染的时候会导致错误,这里应该使用特殊的is属性

1
2
3
<table>
<tr is='my-row'></tr>
</table>

当然,如果使用来自以下来源之一的字符串模板,这些限制将不适用

  • <script type='text/x-template'>
  • JavaScript内联模板字符串
  • .vue组件

前两个模板并不是Vue的推荐模式

组件的属性和事件

就像HTML使用元素会有一些属性一样,自定义组件也可以拥有属性。当一个组件中,使用了其它自定义组件,就会利用子组件的属性和事件来和父组件进行数据交流。

父子组件之间的通信就是props down,events up,父组件通过属性props向下传递数据给子组件,子组件通过事件event给父组件发送消息。

譬如,当子组件需要某个数据,就在内部定义一个prop属性,然后父组件就像给html元素指定特性值一样,把自己data中的属性传递给子组件的这个属性。

而当子组件内部发生什么事情的时候,就通过自定义事件来把这个事情涉及到的数据暴露出来供父组件处理。

1
2
<my-component v-bind:foo='baz' v-on:event-a='doThis(arg1,,,)'>
</my-component>

如上代码,foo是自定义组件内部定义的要给prop属性,baz是父组件的一个data属性,event-a是子组件定义的一个事件,doThis是父组件的一个方法。

属性Props

Vue组件通过props属性来声明一个自己的属性,然后父组件就可以往里面传递数据。

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
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Component</title>
<script src="./vue.js"></script>
</head>
<body>
<div id='app'>
<my-component my-message='test'></my-component>
</div>
<script>
let app = new Vue({
el: '#app',
data: {},
components: {
'my-component' : {
template: '<div>{{ message }}</div>',
props: ['myMessage'],
data() {
return {
message: '父组件传递的内容:' + this.myMessage
}
}
}
}
})
</script>
</body>
</html>

注意由于HTML特性是不区分大小写的,所以在使用自定义组件并传递属性时,对于存在大写的属性名,需要转变为短横线隔开的形式

v-bind绑定属性值

使用v-bind绑定属性值时存在一个特性,一般情况下使用v-bind给元素特性传递值时,Vue会将双引号中的内容当作一个表达式。

动态绑定特性值

当我们使用v-bind将父组件的属性绑定到子组件后,父组件中的数据改变就能反应到子组件了。但是我们如果想在子组件中来修改父组件中的属性,那么就要依情况而定了。

  • 当父组件传递的属性是引用类型时,在子组件中修改相应的属性就会导致父组件相应属性的更改

    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
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>Component</title>
    <script src="./vue.js"></script>
    </head>
    <body>
    <div id='app'>
    <div>这是父组件的数组:{{ parentArray }}</div>
    <my-component :child-array='parentArray'></my-component>
    </div>
    <script>
    let app = new Vue({
    el: '#app',
    data: {
    parentArray: []
    },
    components: {
    'my-component' : {
    template:
    `<div>
    父组件传递的属性: {{ childArray}}</br>
    <button type='button' @click='changeArray'>
    点击改变父属性</button>
    </div>`,
    props: ['childArray'],
    data() {
    return {
    counter: 1
    }
    },
    methods: {
    changeArray() {
    this.childArray.push(this.counter++)
    }
    }
    }
    }
    })
    </script>
    </body>
    </html>
  • 当父组件传递值为基本类型时,在子组件中更改这个属性就会报错。正确的做法是在父组件中绑定属性值时,加上.sync修饰符

    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
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>Component</title>
    <script src="./vue.js"></script>
    </head>
    <body>
    <div id='app'>
    <div>这是父组件的属性:{{ parentCount }}</div>
    <my-component :child-count.sync='parentCount'></my-component>
    </div>
    <script>
    let app = new Vue({
    el: '#app',
    data: {
    parentCount: 1
    },
    components: {
    'my-component' : {
    template:
    `<div>
    父组件传递的属性: {{ count }}</br>
    <button type='button' @click='changeArray'>
    点击改变父属性</button>
    </div>`,
    props: ['childCount'],
    data() {
    return {
    count: this.childCount
    }
    },
    methods: {
    changeArray() {
    this.count++;
    this.$emit('update:childCount', this.count)
    }
    }
    }
    }
    })
    </script>
    </body>
    </html>

子组件对prop的正确操作

一般来说,是不建议在子组件中对父组件传递来的属性进行操作的,如果真的有这种需求,可以使用一下的方法:

  • 如果父组件传递的是一个基本类型的值,那么可以在子组件中创建一个新的属性,并以传递进来的值进行初始化,然后操作这个新的属性
  • 如果父组件传递的是一个引用类型的值,为了避免更改父组件中相应的数据,最好对引用类型进行深复制

Prop验证

我们可以给组件的props属性添加验证,当传入的数据不符合要求时,Vue会发出警告

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
Vue.component('example', {
props: {
// 基础类型检测,如果为null,则表示任何类型都可以
propA: number,
// 多种类型
propB: [String, Number]
// 必须传递且是字符串
propC: {
type: String,
required: true
},
// 数字,并且有默认值
propD: {
type: Numbler,
default: 100
},
// 数组或对象,默认值由一个工厂函数返回
propE: {
type: Object,
default: function() {
return {
message: 'hello'
}
}
},
// 自定义验证函数
propF: {
type: Number,
validator: function(value) {
return value > 10
}
}
}
})

type可以是下面原生构造器:

  • String
  • Number
  • Array
  • Object
  • Boolean
  • Function
  • Symbol

type也可以是一个自定义构造器函数,Vue会使用instanceof来检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 自定义Person构造器
function Person(name, age) {
this.name = name
this.age = age
}
Vue.component('my-component', {
template: `<div>名字: {{ person-prop.name }}, 年龄: {{ person-prop.age }} </div>`,
props: {
person-prop: {
type: Person // 指定类型
}
}
})
new Vue({
el: '#app2',
data: {
person: 2 // 传入Number类型会报错
}
})

非Prop类型的属性

可以像在html标签中添加data-开头的自定义属性一样,给自定义组件添加任意属性。而且不一定非要是data-开头的,这样做的话,Vue会把这个属性放在自定义组件的根元素上。一个自定义组件的模板只能有一个根元素

如果父组件向子组件的非prop属性传递了值,那么这个值就会覆盖子组件模板中的特性

1
2
3
4
5
6
7
8
9
10
11
<div id='app'>
<my-component att='helloParent'></my-component>
</div>
<script>
Vue.component('my-component', {
template: `<div att='helloChild'>子组件原有特性会被覆盖</div>`
})
new Vue({
el: '#app'
})
</script>

上面渲染的结果div的att属性是helloParent

不过class和style属性并不会被覆盖,而是采用合并原则。

自定义事件

通过prop属性,父组件可以向子组件传递属性,而子组件的自定义时间就是用来将内部数据报告给父组件的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div id="app3">
<my-component2 v-on:myclick="onClick"></my-component2>
</div>
<script>
Vue.component('my-component2', {
template: `<div>
<button type="button" @click="childClick">点击我触发自定义事件</button>
</div>`,
methods: {
childClick () {
this.$emit('myclick', '这是我暴露出去的数据', '这是我暴露出去的数据2')
}
}
})
new Vue({
el: '#app3',
methods: {
onClick () {
console.log(arguments)
}
}
})
</script>

传递的过程如下:

  • 子组件在自己的方法中触发自定义事件,并将需要发送的数据发送

    1
    this.$emit('myclick', '这是我暴露出去的数据', '这是我暴露出去的数据2')
    • 第一个参数是自定义事件名
    • 后面的参数是想要向父组件发送的数据
  • 父组件通过利用v-on绑定的自定义事件处理函数处理子组件传递的数据

注意:在使用v-on绑定事件处理方法时,不应该传递进任何参数,不然,子组件暴露出来的数据将无法获取

如果想要在某个组件的根元素上监听一个原生事件,可以使用.native修饰v-on

1
<my-component v-on:click.native="doTheThing"></my-component>

自定义组件的v-model

我们之前在父子组件之间进行值的交换的时候,使用的是这样的方法

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
//父组件
<template>
<div class="father">
<p>DAD:{{sendData}}</p>
<son :data="sendData" @revert="_getSonSendData"></son>
</div>
</template>

<script>
import son from './son'
export default {
name: "testmodel",
components:{son},
data(){
return {
sendData:'儿子快回家吃饭'
}
},
methods:{
_getSonSendData(data){
this.sendData = data
}
}
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//子组件
<template>
<div class="son">
<p>SON:我father对我说“{{data}}”</p>
<button @click="_revert">回复</button>
</div>
</template>

<script>
export default {
name: "son",
props:{
data:{
type:String
}
},
methods:{
_revert(){
this.$emit('revert','success')
}
}
}
</script>

这种方式有一个重大的问题是,父组件给子组件传递的值只能使用v-bind绑定,而且我们对于子组件返回的值每次都需要定义一个方法接收处理。

而Vue提供了一个语法糖,就是v-model,我们先来看看怎么用

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
//父组件
<template>
<div class="father">
<p>DAD:{{sendData}}</p>
<son v-model='sendData'></son>
</div>
</template>

<script>
import son from './son'
export default {
name: "testmodel",
components:{son},
data(){
return {
sendData:'儿子快回家吃饭'
}
},
methods:{
_getSonSendData(data){
this.sendData = data
}
}
}
</script>
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
//子组件
<template>
<div class="son">
<p>SON:我father对我说“{{data}}”</p>
<button @click="_revert">回复</button>
</div>
</template>

<script>
export default {
name: "son",
props:{
data:{
type:String
}
},
model:{
prop: 'data',
event: 'change'
}
methods:{
_revert(){
this.$emit('change','success')
}
}
}
</script>

这样子就做到了不需要再父组件中用方法接收子组件传过来的值来改变父组件中$data中的值

特别要注意的是子组件中的model需要定义两个值:

  • prop:表示使用那个子组件属性来接收父组件v-model绑定的值
  • event:表示事件,只要这个事件被触发,父组件v-model绑定的值就会被我们返回的数据替代

动态组件

我们可以使用保留的<component>元素,动态的绑定到它的is特性,可以让多个组件使用同一个挂载点,并且可以动态切换

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
 <div id="app6">
<select v-model="currentComponent">
<option value="home">home</option>
<option value="post">post</option>
<option value="about">about</option>
</select>
<component :is="currentComponent"></component>
</div>
<script>
new Vue({
el: '#app6',
data: {
currentComponent: 'home'
},
components: {
home: {
template: `<header>这是home组件</header>`
},
post: {
template: `<header>这是post组件</header>`
},
about: {
template: `<header>这是about组件</header>`
}
}
})
</script>

保留切换出去的组件,避免重新渲染

如果把动态切换出去的组件保存在内存中,可以保留它的状态或避免重新渲染。为此我们可以在<component>外面加上keep-alive指令参数

1
2
3
4
5
<keep-alive>
<component :is="currentComponent">
<!-- 非活动组件将被缓存! -->
</component>
</keep-alive>
文章目录
  1. 1. 组件的基本使用
    1. 1.1. 注册组件
    2. 1.2. 模板的要求
    3. 1.3. 组件中的data必须是函数
    4. 1.4. DOM模板解析的限制
  2. 2. 组件的属性和事件
  3. 3. 属性Props
    1. 3.1. v-bind绑定属性值
    2. 3.2. 动态绑定特性值
    3. 3.3. 子组件对prop的正确操作
    4. 3.4. Prop验证
    5. 3.5. 非Prop类型的属性
    6. 3.6. 自定义事件
    7. 3.7. 自定义组件的v-model
  4. 4. 动态组件
    1. 4.1. 保留切换出去的组件,避免重新渲染