首页
所有内容 组件 文章正文
网友投稿
805
2022-05-29
组件是Vue.js最推崇的,也是最强大的功能之一,核心目标是为了代码开发的重用性。我们可以把组件代码按照 template、style、script 的拆分方式,放置到对应的.vue文件中。本章节我们就来学习有关Vue组件的基础知识。
5.1 组件简介
组件的本质就是为了拆分Vue实例的代码量。我们也可以把所有的组件代码写到Vue实例中,那样的话,实例代码则显的臃肿不堪,冗长无比,这并不是我们想要的,所以,我们根据组件的功能性,以不同的的功能来划分不同的组件,将来我们需要什么样的功能,就可以取调用对应的组件即可,从而达到组件的可重用性和灵活性。
5.1.1 组件的定义
组件(Component)是Vue.js最强大的功能之一,组件可以扩展HTML元素,封装可重用代码,在较高层面上,组件是自定义元素,Vue.js的编译器为他添加特殊功能。有些情况下,组件也可以表现用 is 特性进行了扩展的原生的HTML元素,所有的Vue组件同时也都是Vue实例,所以可以接受相同的选项对象(除了一些根级特有的选项),并提供相同的生命周期钩子。
vue组件的功能:
• 能够把页面抽象成多个相对独立的模块;
• 实现代码重用,提高开发效率和代码质量,使得代码易于维护。
5.1.2 组件的类型
• 组件按照注册方式不同,分为全局组件和局部组件。
什么情况下需要将组件注册为全局组件?一般是一些基础组件,频繁(3 次以上)需要用到的,需要全局注册。例如常用的 dialog(对话框)组件,search(搜索框) 组件,toast(弹出框) 组件,message(消息框)组件等。
一般情况下的组件应该是局部组件,这样会极大的减少构建应用后的代码体积,但是对于频繁使用的组件就显得麻烦了,所以建议,组件使用频率低,组件比较大的时候注册为局部组件。比如 table 组件,chart 组件等。
• 组件按照有无自己的状态,可以分为函数式(无状态)组件和普通(有状态)组件。
什么情况下需要将组件写为函数式组件?一般是无状态 (没有响应式数据)的组件可以注册成函数式组件,好像不用函数式组件也可以呀,为啥要注册成函数式组件?当一个组件是一个函数式组件的时候,它没有管理任何状态,也没有监听任何传递给它的状态,也没有生命周期方法。实际上,它只是一个接受一些 prop 的函数,所以渲染开销也低很多。
• 组件按照是否动态分类:可以分为动态组件和普通(非动态)组件。
什么情况下需要将组件写为动态组件?一般是组件之间需要切换的情况下。但是不用动态组件也可以,那为啥要动态组件?当你导入 Dynamic Component1(动态组件) 从 1 写到 10 的时候,然后 template 再写 DynamicComponent 10 次的时候,它的好处就出来了。
• 组件按照是否异步分类:可以分为异步组件和普通(非异步)组件。
在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。
什么情况下需要将组件写为异步组件?一般是需要从服务器加载数据的组件,且需要多个地方使用的,因为它会把结果缓存起来供未来重渲染。还有就是大家使用最多的是在 Vue Router 里使用,异步组件结合 Webpack 的代码分割功能,可以轻松实现路由组件的懒加载。
• 组件按照是否循环引用分类:可以分为递归组件和普通(非递归)组件。
组件是可以在它们自己的模板中调用自身的。不过它们只能通过 name 选项来做这件事。一般是组件需要调用自身的时候,比如树组件,侧边栏路由组件等,才会使用递归组件。
表5.1 组件的分类
分类标准
分类1
分类2
注册方式
全局组件
局部组件
有无自己的状态
无状态组件(函数式组件)
有状态组件(普通组件)
是否动态
动态组件
非动态组件
是否异步
异步组件
非异步组件
是否循环使用
递归组件
普通组件
以上是我们组件的分类,大家现在只需要了解组件的分类,后面我们会一一详解。
5.1.3 组件的构成要素
Vue.js的组件可以理解为预先定义好行为的ViewModel类。一个组件就像一个Vue实例一样,也可以预定义很多选项,但最核心的是以下几个:
• 模板(template)——模板声明了数据和最终展现给用户的DOM之间的映射关系。
• 初始数据(data)——一个组件的初始数据状态。对于可复用的组件来说,通常是私有数据。
• 接受的外部参数(props)——组件之间通过参数来进行数据的传递和共享。参数默认是单向绑(由上至下),但也可以显式声明为双向绑定。
• 方法( methods)——对数据的改动操作一般都在组件的方法内进行。可以通过 v-on指令将用户输入事件和组件方法进行绑定。
• 生命周期钩子函数(lifecycle hooks)——一个组件会触发多个生命周期钩子函数,比如created、attached、destroyed 等。在这些钩子函数中,我们可以封装一些自定义的逻辑。
在组件的构成要素中,我们发现data,methods,以及生命周期函数这些都是跟我们的Vue实例相似的,其实在功能上的初衷也是一样的,但是在使用上还是有差别的,接下来我们就来学习组件的封装和使用。
5.2组件封装和使用
5.2.1 封装组件
封装一个组件有以下三种方式:
表5.2封装组件的方法
方法
方法描述
Vue.extend()
使用extend()方法封装一个组件
Template
使用template标签来封装一个组件
Script
使用script标签来封装一个组件
封装好组件就可以直接使用组件了吗,我们仍然需要一系列的步骤,来看Vue组件的使用步骤:
1. 使用任意的封装方法进行封装组件的内容;
2. 使用Vue.component()方法进行组件的注册;
3. 根据业务需要进行数据的通信;
了解完步骤后,我们知道组件根据注册方式的不同,分为全局和局部,接下来我们使用全局注册来演示Vue组件的封装的三种方式,现在先来看第一种方式。
例5-01 Demo0501.html
1
2
3
4
5
6
7
Vue组件的第一种封装方式 8
9
15
这是根组件 16
17
18
19
20
21
22 //第一种使用extend()封装Vue组件
23 //1、封装组件的模板
24 let com1 = Vue.extend({
25 template: "
这是使用h3定义的组件 "
26 });
27 //2、使用Vue.component(组件的名字,组件的模板)方法注册组件
28 Vue.component('hello', com1);
29 let vm = new Vue({
30 el: '#app',
31 });
32
33
程序的运行结果如下:
图 5- 01 使用Vue.extend()方法封装的组件模板
根据例题Demo0501,我们来总结以下注意事项:
1、 使用Vue.extend()方法来封装组件模板,模板内容必须使用{}括起来。根据编程常识,我们知道在{}中可以有多个选项,不止template一个选项。在5.1.3中我们学习了模板的构成要素,那么这些构成要素就可以放在{}中;
2、 使用Vue.component("组件的名字",模板) 注册成为组件,在给组件起名字的时候是有注意事项的。在注册时,注册的名字是驼峰命名法,则在使用的时候,必须把大驼峰变为小写,中间加-,例如注册的名字为myCom,则在根容器中引入该名字标签的时候要使用 。如果注册时没有使用驼峰命名,则不需要进行此转化,例如注册时hellocom,则在根标签使用的时候就是 。
接下来我们来看第一种封装方式的简化版本:
例5-02 Demo0502.html
1
2
3
4
5
6
7
Vue组件的第一种封装方式 8
9
15
这是根组件 16
17
18
19
20
21
22 //第一种的简化版本,封装一个p标签和一个按钮
23 //注意:如果封装的模板中有两个html元素,那么该封装必须有个根标签,一般我们使用div作根标签
24 Vue.component("myCom", Vue.extend({
25 template: "
"
26 }));
27
28 let vm = new Vue({
29 el: '#app',
30 });
31
32
程序的运行结果如下:
图 5- 02 这是使用extend()封装的简化版本
通过例5-02,进行如下总结:
我们发现组件封装的模板是有html和css组成的;同时,也发现,在双引号中写html元素是一件很不方便的事情,为了解决这个问题,接下来我们使用template标签来实现第二种方式的封装;
例5-03 Demo0503.html,使用template标签进行封装;
1
2
3
4
5
6
7
Vue组件的第二种封装方式 8
9
15
这是根组件 16
17
18
19
20
21
25
26
27
28
29
30 //注册,Vue.component(组件的名字,组件的模板);
31 Vue.component("hellocom", { template: "#hellocom" });
32
33 let vm = new Vue({
34 el: '#app',
35 });
36
37
38
程序的运行结果如下:
图 5- 03 template标签封装模板
根据例5-03我们总结以下:
template标签必须写到body中,给template标签一个id属性,方便注册的时候通过id引入;
无论是使用哪种方式封装模板,如果模板中有多个html元素,都必须使用根标签包裹;
接下来我们来看第三种方式封装组件:
例5-04 Demo0504.html,使用script标签进行封装;
1
2
3
4
5
6
7
8
Vue组件的第三种封装方式 9
10
16
这是根组件 17
18
19
20
21
22
23
24
30
31
32 //全局方式注册组件
33 Vue.component("myCom", Vue.extend({
34 template: "#myCom"
35 }));
36 let vm = new Vue({
37 el: '#app',
38 });
39
40
41
程序的运行结果如下:
图 5-04 这是使用script标签封装的组件
通过demo0504,总结如下:
使用script标签封装组件模板,最好在body标签外部定义,这样能很好的识别;必须指定type='text/x-template',声明id方便注册的时候通过id引入。
以上就是我们封装组件的三种使用方式,同时我们也学习了组件的使用步骤,第一,要封装内容,第二,注册组件,目前我们使用的是全局注册,第三,根据业务进行逻辑处理,这个后续讲解。
5.2.2 注册组件
在上一节中,我们使用三种封装方式以及全局注册对组件的使用有了清晰的了解,接下来,详细讲解组件的注册。
组件的注册有两种,全局注册和局部注册。
全局注册使用Vue.component(组件的注册名字,组件),使用该方法注册的组件可以在多个Vue实例中共享,我们来看实例:
例5-05 Demo0505.html,全局注册组件。
1
2
3
4
5
6
7
8
全局注册组件 9
10
17
实例1的根组件 18
19
20
21
实例2 22
26
27
28
29
30
36
37
38 //全局方式注册组件,多个实例共享
39 Vue.component("myCom", Vue.extend({
40 template: "#myCom"
41 }));
42 //下面是两个Vue实例
43 let vm1 = new Vue({
44 el: '#app',
45 });
46 let vm2 = new Vue({
47 el: "#com"
48 });
49
50
程序的运行结果如下:
图 5-05 这是全局注册组件
通过例5-05,总结如下:
使用Vue.component()实现全局注册,全局注册的组件可以共享;一个页面可以写多个Vue实例,不同的实例控制不同的DOM元素。接下来,我们来看看局部组件:
例5-06 Demo0506.html,局部注册组件。
1
2
3
4
5
6
7
全局注册组件 8
9
16
实例1的根组件 17
18
19
20
实例2 21
25
26
27
33
34
35 //下面是两个Vue实例
36 let vm1 = new Vue({
37 el: '#app',
38 components: {
39 'myCom': {
40 template: '#myCom'
41 }
42 },//components是component的复数形式
43 });
44 let vm2 = new Vue({
45 el: "#com"
46 });
47
48
49
程序的运行结果如下:
图 5-06 局部注册组件
通过demo0506结果我们发现,在两个实例中都使用了新注册的组件,但是只在实例1中显示,这说明局部注册的组件不具备共享性。同时发现,局部组件的注册的时候,需要在某一个Vue实例中使用components属性,那么在哪个Vue实例中注册就只能在哪个Vue实例中共享。
想一想:全局注册的组件换个页面引入还可以使用吗?
全局注册的组件只在当前页面的多个Vue实例中共享,并不能跨页面。
5.2.3 开发组件
通过对组建的初步了解,上述案例都是在html页面中定义组件,其实,真正在开发的时候,我们是把根据功能划分不同的组件,每个组件都是以.vue结尾的文件,每个文件都是有三部分组成,分别是template,script,style部分,如下所示:
1
2
5
6
7
8 export default {
9 data() {
10 return {
11 //写组件的数据
12 };
13 },
14 methods: {
15 //写组件的方法
16 },
17 };
18
19
20
21 div {
22 width: 200px;
23 height: 100px;
24 background-color: pink;
25 }
26
以上的写法,我们在学习了vue与webpack结合后,就会大量使用,现在大家先做了解。
5.3组件数据通信
在实际开发过程中,Vue.js中的组件会与所在的环境进行通信,总结起来,组件通信有三种数据传递方式:
• props
• 组件通信
• slot
接下来我们分别对每一种数据通信进行详细的阐述。
5.3.1 props
"props”是组件数据的一个字段,期望从父组件传下来数据。因为组件实例的作用域是孤立的,这意味着不能并且不应该在子组件的模板内直接引用父组件的数据,所以子组件需要显式地用props选项来获取父组件的数据。props选项可以是字面量,也可以是表达式,还可以绑定修饰符。下面我们详细看一下它是如何使用的。
1.字面量语法
我们可以给子组件传一个常量值,也就是字面量,来看看子组件如何接受:
接下来演示普通常量值的传递,如例9-15所示。
例5-07 Demo0507.html
1
2
3
4
5
6
7
8
props字面量的传值 9
10
16
这是根组件 17
18
19
20
21
22
23
24
hello,Vue
25 {{hello}}
26
27
28
29 let vm = new Vue({
30 el: '#app',
31 components: {
32 'test': {
33 props: ['hello'],//使用props这个属性来接受传过来的值,props是个数组,里面写的是自定义属性的名字
34 template: '#myCom',
35 }
36 },
37 });
38
39
40
程序的运行结果如下:
图 5-07 传递字面常量
例5-07中,通过在子组件上自定义了一个属性“hello”,然后给这个属性一个字面常量,在子组件的内部通过props属性来接受。接受到后,可以正常使用插值表达式进行显示。
注意:props这个数组里写的是自定义属性的名字,所以是字符串。
自定义属性的名字,如果是kebab-case命名法,则在props接受得时候转为驼峰命名,例如
,则在子组件使用props接受得时候必须写成props:[‘myHello’]
2.动态语法
类似于用v-bind将HTML 特性绑定到一个表达式,我们也可以用v-bind将动态将父组件得数据绑定到自定义属性中。每当父组件的数据变化时,该变化也会传导给子组件,代码示例如下:
接下来演示动态绑定,如例5-08所示。
例5-08 Demo0508.html
1
2
3
4
5
6
7
8
props字面量的传值 9
10
16
这是根组件 17
18
19
20
21
22
23
24
hello,Vue
25 {{hello}}
26
27
28
29 let vm = new Vue({
30 el: '#app',
31 data: {
32 msg: "我是父组件得数据"
33 },
34 components: {
35 'test': {
36 props: ['hello'],//使用props这个属性来接受传过来的值,props是个数组,里面写的是自定义属性的名字
37 template: '#myCom',
38 }
39 },
40 });
41
42
43
程序的运行结果如下:
图 5-08 动态传递父组件的数据
例5-08中,如果自定义属性要绑定父组件data中的数据,则需要使用v-bind,v-bind简写为冒号。
3.props类型
通常,我们只看到了以字符串数组形式列出的 props,例如,props: ['title', 'likes', 'isPublished', 'commentIds', 'author']。但是,如果你希望每个 prop 都有指定的值类型。这时,你可以以对象形式列出 prop,这些 property 的名称和值分别是 prop 各自的名称和类型:
1 props: {
2 title: String,
3 likes: Number,
4 isPublished: Boolean,
5 commentIds: Array,
6 author: Object,
7 callback: Function,
8 contactsPromise: Promise // or any other constructor
9 }
type类型有如下:
• String
• Number
• Boolean
• Object
• Function
• Array
4.props验证
我们可以为组件的 prop 指定验证要求,如果有一个需求没有被满足,则 Vue 会在浏览器控制台中警告你。为了定制 prop 的验证方式,你可以为 props 中的值提供一个带有验证需求的对象,而不是一个字符串数组。例如:
1 let vm = new Vue({
2 el: '#app',
3 data: {
4 msg: "我是父组件得数据"
5 },
6 components: {
7 'test': {
8 props: {
9 // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
10 propA: Number,
11 // 多个可能的类型
12 propB: [String, Number],
13 // 必填的字符串
14 propC: {
15 type: String, //数据类型
16 required: true //必填项
17 },
18 // 带有默认值的数字
19 propD: {
20 type: Number,
21 default: 100 //默认值
22 },
23 // 带有默认值的对象
24 propE: {
25 type: Object,
26 // 对象或数组默认值必须从一个工厂函数获取
27 default: function () {
28 return { message: 'hello' }
29 }
30 },
31 // 自定义验证函数
32 propF: {
33 validator: function (value) {
34 // 这个值必须匹配下列字符串中的一个
35 return ['success', 'warning', 'danger'].indexOf(value) !== -1
36 }
37 }
38 },
39 template: '#myCom',
40 data() {//这个是组件自己的数据
41 return {
42 hi: 'hello'
43 }
44 },
45 }
46 },
47 });
48
49
注意:props的数据来自父级,data中数据是组件自己的数据。
子组件中的data和Vue实例中的data是不一样的,子组件的data是个函数,而且数据必须定义在return的对象中。
5.单项数据流
所有的 prop 都使得父子 prop 之间形成了一个单向向下的数据流:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
父组件===>子组件:vue允许的,会主动触发的,也叫正向传递。
子组件===>父组件:vue允许的,不会主动触发,需要手动(被动)触发,叫做逆向传递。
另外,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 通过prop传递的值,如果你这样做了,Vue 会在浏览器的控制台中发出警告。
例5-09 Demo0509.html,通过
44
1
2
3
4
5
6
7
props字面量的传值 8
9
15
这是根组件 16
17
18
19
20
21
22
23
hello,Vue
24 {{hello}}
25
26
27
28 let vm = new Vue({
29 el: '#app',
30 data: {
31 msg: "我是父组件得数据"
32 },
33 components: {
34 'test': {
35 props: ['hello'],//使用props这个属性来接受传过来的值,props是个数组,里面写的是自定义属性的名字
36 template: '#myCom',
37 }
38 },
39 });
40
41
42
程序的运行结果如下:
图 5-09 使用验证方式接受传值
如例5-09 显示,我们使用的是验证方式接受父组件传过来的值。接下来我们对父组件穿过的数据及性能修改,控制台警告如下:
图 5- 10 通过子组件修改父组件的数据
5.3.2 组件通信
上一节我们研究了父组件将值传递给子组件,叫做正向传值,子组件将值传递给父组件,叫做逆向传值;需要借助自定义事件。
vue.js 中允许正向传值,所以正向传值不需要条件触发,是主动的;逆向传值,也是允许的,但是需要主动(手动)触发,需要借助事件触发器:
• $on():监听事件
• $emit():把事件沿着作用域链向上传递
使用步骤:
第一步:在父组件中引用的子组件的标签上,自定义事件;
这是根组件 显示子组件传过来的值:{{sonmsg}}
let vm = new Vue({
el: '#app',
data: {
msg: "我是父组件得数据",
sonmsg: "" //用来接受从子组件传过来的值
},
//2、定义一个函数
methods: {
fn(val) {
this.sonmsg = val; //val 是子组件在触发该函数的时候传过来的参数,把该参数传给父组件中的data
}
},
});