# Vue2教程 - 14 组件
什么是组件?
前面在进行学习的时候,已经使用了组件,在 App.vue
组件中完成的,但是在项目中只使用了这一个组件,作为整个项目的根组件。
在正常的项目中,是将 App
作为根组件的,然后在 App
中通过路由(后面学习)来控制页面的切换,实际控制的是组件的切换;页面中展示的内容,也是封装成一个个组件,利于功能的划分和代码的复用。最终构成整个前端的 SPA(Single Page Application) 单页面应用。
下面来正式学习一下组件的使用。
# 14.1 创建组件
现在我们来创建一个组件,然后在 App.vue
根组件中引入并使用这个组件。
# 1 创建组件
在 src
目录下创建一个 components
目录,然后在 components
目录下创建一个 HomePage.vue
文件,后缀名为 .vue
。
在 HomePage.vue
文件中输入如下内容:
<!-- 1.组件模板 -->
<template>
<div>
<h1>HomePage组件</h1>
</div>
</template>
<!-- 2.组件业务逻辑 -->
<script>
export default {
name: "HomePage" //可以省略
}
</script>
<!-- 3.编写组件样式,scoped表示样式只对当前组件生效 -->
<style scoped>
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
组件分为三个部分,在 HelloWorld
中已经讲解过了。
# 2 引入和使用组件
现在在 App.vue
页面引入上面创建的组件,使用 import
进行引入:
<template>
<div id="root">
<!-- 使3.用组件 -->
<HomePage />
</div>
</template>
<script>
// 1.引入HomePage
import HomePage from '@/components/HomePage.vue';
export default {
name: 'App',
components: {
HomePage // 2.声明使用组件
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- 首先使用
import
引入组件,路径中的@
表示src
目录; - 在 App 组件的
components
属性中声明使用组件,如果有多个,用,
分隔; - 然后就可以使用组件了:
<HomePage></HomePage>
。
# 3 组件中的data
前面也已经介绍了,可以在 组件中定义 data
、methods
、filters
、computed
等等。
关于组件的 data
有几点说明:
- 组件的
data
和 Vue 实例的data
有点不一样,Vue 实例中的data
可以为一个对象,但是组件中的data
必须是一个方法; - 组件中的
data
除了必须为一个方法之外,这个方法内部,还必须返回一个对象才行; - 组件中的
data
数据,使用方式和Vue实例中的data
使用方式完全一样,使用this.
就可以直接访问data
中的数据。
为什么组件中的数据必须返回一个函数?
如果
data
不是一个函数,而是一个对象,那么这个对象会在所有组件实例之间共享,导致一个实例对数据的更改会影响到其他实例。通过返回一个函数,每个组件实例都可以调用这个函数来获取自己的数据副本,从而保持数据的独立性。
# 14.2 父组件向子组件传值
在子组件中无法访问父组件中定义的数据和方法。
那么父子组件如何进行数据传递呢?
父组件通过属性绑定的方式给子组件传值,子组件在 props
属性中定义接收的值。
举个栗子:
我们再定义一个子组件 ChildCom.vue
然后在 HomePage.vue
组件中引入并使用,并传递参数。
# 1 定义子组件
子组件ChildCom.vue
:
<template>
<div>
<span>父组件传递的值:{{ superMsg1 }}, {{ superMsg2 }}</span>
</div>
</template>
<script>
export default {
name: "ChildCom",
props: ['superMsg1', "superMsg2"]
}
</script>
2
3
4
5
6
7
8
9
10
11
12
- 在子组件
ChildCom.vue
中通过在子组件上添加props
属性来接收,可以定义多个参数,上面ChildCom.vue
组件可以接收父组件传递superMsg1
和superMsg2
参数; - 需要确保传递给子组件的
props
名称不与子组件内部定义的data
、computed
属性或方法冲突。 - 组件中,
data
的数据是可读可写的,props
中的数据都是只读的,子组件不可修改。
# 2 父组件传值
父组件 HomePage.vue
:
<!-- 1.组件模板 -->
<template>
<div>
<ChildCom :superMsg1="'Hello'" :superMsg2="superMsg2"></ChildCom>
<button @click="changeMsg">改变父组件的Msg</button>
</div>
</template>
<!-- 2.组件业务逻辑 -->
<script>
import ChildCom from '@/components/ChildCom.vue';
export default {
name: "HomePage",
components: {
ChildCom
},
data() {
return {
superMsg2: "World"
}
},
methods: {
changeMsg() {
this.superMsg2 = "Doubi"
}
},
}
</script>
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
首先引入了子组件并使用,在使用的时候通过数据绑定的方式为给子组件传值,可以使用 v-bind:superMsg1
或 :superMsg1
的方式。
上面给 superMsg1
传递的是一个字符串,然后将 HomePage
组件中的 superMsg2
传递给 ChildCom
组件的 superMsg2
,所以通过点击按钮,修改 HomePage
组件中的 superMsg2
的值,那么传递给 ChildCom
组件的 superMsg2
也会同步修改。
也就是说:父组件传递给子组件的值是无法在子组件进行修改的,但是可以在父组件中修改。
# 14.3 子组件向父组件传值
子组件向父组件传值是通过方法回调的方式实现的,即父组件向子组件传递函数的引用,子组件调用传递的函数,将数据作为函数的参数来实现数据的传递。
父组件向子组件传递函数:
<ChildCom @childClick="childChangeMsg"></ChildCom>
将 childChangeMsg
函数传递给子组件,使用事件绑定的方式,绑定的名称是 childClick
;可以使用多个名称绑定多个函数,那么子组件就可以调用父组件的多个函数。
子组件调动父组件函数:
父组件传递给子组件的函数,不能直接调用,需要通过 this.$emit('函数名称')
来调用父组件传递的函数的。
this.$emit('childClick', arg)
参数的传递 this.$emit()
函数的第一参数是函数绑定的名称,后面的参数可以传递参数,父组件的函数的参数可以直接获取到传递的参数,参数可以多个。
举个栗子:
下面的例子中,父组件将函数传递给子组件,通过点击子组件的按钮,来触发父组件传递的函数,将子组件自己的私有数据传递给父组件。
# 1 定义父组件
父组件 HomePage.vue
:
<template>
<div class="home">
<div>{{ msg }}</div>
<!-- 3.使用子组件 -->
<ChildCom @childClick="childChangeMsg"></ChildCom>
</div>
</template>
<script>
// 1.引入子组件
import ChildCom from '@/components/ChildCom'
export default {
name: 'HomePage',
// 2.注册子组件
components: {
ChildCom
},
data: function () {
return {
msg: 0
}
},
methods: {
// 通过子组件调用父组件的方法,修改父组件中的数据
childChangeMsg(childData) {
this.msg += childData;
}
},
}
</script>
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
父组件在使用子组件的时候通过 @childClick="childChangeMsg"
将函数 childChangeMsg
绑定到指令 childClick
上,在子组件中就可以通过 childClick
调用父组件的函数了。
# 2 定义子组件
子组件 ChildCome.vue
:
点击按钮后,通过调用父组件方法,传递数据给父组件。
<template>
<div>
<button @click="doClick">这是子组件中的按钮</button>
</div>
</template>
<script>
export default {
data: function () {
return {
data: 1,
};
},
methods: {
doClick() {
// 调用父组件中的方法,并传递参数
this.$emit('childClick', this.data)
}
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
参数可以传递多个,也可以传递对象。
# 14.4 标签的ref属性
如果想获取元素和组件的 DOM,可以使用标签的 ref
来实现。
ref
可以添加在普通的 HTML 标签上,也可以添加在组件标签上:
- 添加在普通的HTML标签上,获取的是 DOM 节点;
- 添加在 Vue 组件标签上,获取的是组件实例。
如果在以前,我们会使用 document.getElementById()
的方式来获取到 DOM 元素,但在 Vue 中不推荐。
首先定义子组件 ChildCom.vue
如下:
<template>
<div>
{{ msg }}
</div>
</template>
<script>
export default {
data: function () {
return {
msg: "Hello Doubi",
};
},
methods: {
changeMsg() {
this.msg = "Hello World";
}
}
}
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
然后在父组件中引入子组件,并使用 ref
来获取子组件的属性和调用子组件的方法。
HomePage.vue
:
在下面的代码中,同时给普通的标签和子组件添加 ref
属性,可以分别获取到普通组件的 DOM 和子组件的实例。
<template>
<div class="home">
<span id="my-span" ref="mySpan">逗比笔记</span>
<ChildCom ref="childCom"></ChildCom>
<button @click="testRef">测试ref</button>
</div>
</template>
<script>
import ChildCom from '@/components/ChildCom'
export default {
name: 'HomePage',
components: {
ChildCom
},
methods: {
testRef() {
// 不要使用元素js操作DOM
console.log(document.getElementById('my-span').innerText)
// 获取mySpan标签中的内容
console.log(this.$refs.mySpan.innerText)
// 可以直接获取子组件的属性
console.log(this.$refs.childCom.msg)
// 可以直接调用子组件的方法
this.$refs.childCom.changeMsg();
}
},
}
</script>
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
可以在父组件中通过 this.$refs.ref属性名称
来调用子组件的属性和方法。
# 14.5 组件的切换
这里的切换,是某一个页面中一个部件的切换,在 Vue 中,页面也是组件,但是页面之间的切换,是通过后面的路由来完成的,路由后面再讲解。
例如在登录页面,有两个按钮登录和注册,当点击登录按钮,显示输入登录信息的输入框,点击注册,在同样的位置显示填写注册的信息,其实这是一个登录组件和注册组件的切换,那么如何实现组件的切换呢?
# 1 通过v-if实现
通过在Vue实例中定义一个flag,在组件上通过v-if来获取flag的值来判断组件要不要显示,当点击登录或注册按钮的时候,修改flag的值。
定义两个组件
LoginCom.vue
<template>
<div id="root">
登录
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
#root {
width: 200px;
height: 200px;
background-color: red;
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Register.vue
<template>
<div id="root">
注册
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
#root {
width: 200px;
height: 200px;
background-color: yellowgreen;
}
</style>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
实现切换
在Home.vue中引入两个组件,通过定义 flag
属性,使用 v-if
来实现隐藏和显示。
<template>
<div class="home">
<!-- 点击的时候,修改flag的值 -->
<a href="" @click.prevent="flag=!flag">切换</a>
<!-- 通过flag的值来判断显示哪个组件 -->
<LoginCom v-if="flag"></LoginCom>
<RegisterCom v-else></RegisterCom>
</div>
</template>
<script>
import LoginCom from '@/components/LoginCom'
import RegisterCom from '@/components/RegisterCom'
export default {
name: 'HomePage',
data: function() {
return {
flag: true
}
},
components: {
LoginCom,RegisterCom
}
}
</script>
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
效果如下:
# 2 使用component标签
使用 <component>
标签来实现切换。
<component :is="componentName"></component>
:is
属性指定的 componentName
是组件的名称,是一个字符串,只要这个值是哪个组件的名称,则在这个标签的位置就显示哪个组件。
这样我们在 data
中定义一个变量用来保存显示的组件的名称,当点击登录
或注册
按钮的时候,修改这个变量的值就可以了。
Home.vue代码:
<template>
<div class="home">
<!-- 点击的时候,修改componentName的值 -->
<a href="" @click.prevent="componentName = 'LoginCom'">登录</a>
<a href="" @click.prevent="componentName = 'RegisterCom'">注册</a>
<!-- component标签 是一个占位符, :is属性,可以用来指定要展示的组件的名称 -->
<component :is="componentName"></component>
</div>
</template>
<script>
import LoginCom from '@/components/LoginCom'
import RegisterCom from '@/components/RegisterCom'
export default {
name: 'HomePage',
data: function () {
return {
componentName: 'LoginCom' // 当前 component 中的 :is 绑定的组件的名称
}
},
components: {
LoginCom, RegisterCom
}
}
</script>
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
当点击两个按钮的时候,通过修改 componentName
的值,使用 <component>
实现了组件的切换。
# 14.6 组件切换的动画
组件的切换很简单,只需要将 <component>
标签使用 <transition>
标签包裹,然后在编写动画过渡即可。
在 <transition>
上可以设置切换的模式,out-in
表示第一组件出去以后,第二个组件才能进来,不会同时看到两个组件。
HomeVue.vue
<template>
<div class="home">
<!-- 点击的时候,修改componentName的值 -->
<a href="" @click.prevent="componentName = 'LoginCom'">登录</a>
<a href="" @click.prevent="componentName = 'RegisterCom'">注册</a>
<!-- component标签 是一个占位符, :is属性,可以用来指定要展示的组件的名称 -->
<!-- 通过 mode 属性,设置组件切换时候的 模式 -->
<transition mode="out-in">
<component :is="componentName"></component>
</transition>
</div>
</template>
<script>
import LoginCom from '@/components/LoginCom'
import RegisterCom from '@/components/RegisterCom'
export default {
name: 'HomePage',
data: function () {
return {
componentName: 'LoginCom' // 当前 component 中的 :is 绑定的组件的名称
}
},
components: {
LoginCom, RegisterCom
}
}
</script>
<style scoped>
.v-enter,
.v-leave-to {
opacity: 0;
transform: translateX(150px);
}
.v-enter-active,
.v-leave-active {
transition: all 0.5s ease;
}
</style>
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
mode 的默认值是 mode="in-out"
:同时开始进入和离开的过渡,但在实际的动画效果中,可能会看到新旧元素的重叠。