5 组件化开发
5.1 认识组件化
组件化开发:
- 如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展
- 将一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。
vue组件化的思想
它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用
任何的应用都会被抽象成一颗组件树。
让我们的代码更加方便组织和管理,并且扩展性也更强。
5.2 注册组件
5.2.1 注册组件的基本步骤
涉及到的函数的解析
-
参数:
{Object} options
用法:
使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。
data
选项是特例,需要注意 - 在Vue.extend()
中它必须是函数,一个组件的data
选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝组件是可复用的 Vue 实例,所以它们与
new Vue
接收相同的选项,例如data
、computed
、watch
、methods
以及生命周期钩子等。仅有的例外是像el
这样根实例特有的选项。
Vue.component( id, [definition] )
参数:
{string} id
{Function | Object} [definition]
用法:
注册或获取全局组件。注册还会自动使用给定的
id
设置组件的名称1
2
3
4
5
6
7
8// 注册组件,传入一个扩展过的构造器
Vue.component('my-component', Vue.extend({ /* ... */ }))
// 注册组件,传入一个选项对象 (自动调用 Vue.extend)
Vue.component('my-component', { /* ... */ })
// 获取注册的组件 (始终返回构造器)
var MyComponent = Vue.component('my-component')
-
组件使用的分为三个步骤
- 创建组件构造器
- 注册组件
- 使用组件
注意:在Vue实例放入范围内使用组件(即在绑定的dom元素的范围内使用)
- 代码实现;
1 | <div id="app"> |
5.2.2 注册组件步骤解析
Vue.extend():
调用Vue.extend()创建的是一个组件构造器。
通常在创建组件构造器时,传入template代表我们自定义组件的模板。
该模板就是在使用到组件的地方,要显示的HTML代码。
事实上,这种写法在Vue2.x的文档中几乎已经看不到了,它会直接使用下面我们会讲到的语法糖,但是在很多资料还是会提到这种方式,而且这种方式是学习后面方式的基础。Vue.component():
调用Vue.component()是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称。
所以需要传递两个参数:1、注册组件的标签名 2、组件构造器组件必须挂载在某个Vue实例下,否则它不会生效。(见下页)
我们来看下面我使用了三次

- 小结:
- 创建组件构造器中使用到了模板字符串
- 创建组件时只能传入一个根元素(root element)
- 组件必须挂载在实例对象下面才能执行
- 组件在实例对象下面是可以嵌套使用的``
5.3 组件其他补充
5.3.1 全局组件和局部组件
- 全局组件:通过调用Vue.component()注册组件时,组件的注册是全局的
- 该组件可以在任意Vue实例下使用
- 局部组件:注册的组件是挂载在某个实例中, 那么就是一个局部组件,添加在vue实例的options中component属性中,只能挂载的vue实例使用
注意script代码中是可以存在多个vue实例的,一般开发中只适用一个实例进行开发
代码
1 | <div id="app1"> |
效果展示:
5.3.2 父组件与子组件
- 组件和组件之间存在层级关系,而其中一种非常重要的关系就是父子组件的关系
- 子组件时在注册组件时注册在父组件的组件构造器中,并在父组件的template模板中使用了子组件的组件
- 子组件vue实例对象中如果没有注册组件在vue实例对象中是不能直接使用的
代码:
1 | <div id="app"> |

5.3.3 注册组件语法糖
1 | //1. 原始写法 |
5.3.4 模板的分离写法
模板的分离写法:能将其中的HTML分离出来写,然后挂载到对应的组件上,必然结构会变得非常清晰
模板分离的两种写法
- script标签+type类型为text/x-template+id(表示模板)
- template标签+id
- 绑定方式:
template: '#id'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24<!-- 模板分离 -->
<!-- 1.script标签实现 -->
<script type="text/x-template" id="cpn">
<div>
<h2>我是组件1</h2>
<p>哈哈哈</p>
</div>
</script>
<!-- 2.template标签实现 -->
<template id="cpn">
<div>
<h2>我是组件1</h2>
<p>哈哈哈</p>
</div>
</template>
const app = new Vue({
el: '#app',
components: {
cpn1: {
template: '#cpn'
}
}
})
5.4 组件数据存放
5.4.1 组件如何访问实例数据
- 问题:vue组件模板中的是不能直接访问vue实例对象data属性中的值
解决:vue组件中定义自己的data,来访问实例对象中的数据,注意:这里的data是一个函数,函数返回对象(存储需要访问的实例数据)
组件是一个单独功能模块的封装:
这个模块有属于自己的HTML模板,也应该有属性自己的数据data。
5.4.2 组件中data函数解析
- 原因是在于Vue让每个组件对象都返回一个新的对象,因为如果是同一个对象的,组件在多次使用后会相互影响。使用函数是为每个组件实例对象创建块级作用域,就不会相互影响,冰面新修改其中一个另外一个也被影响
1 | //注册组件 |
5.5 父子组件通信
我们知道子组件是不能引用父组件或者Vue实例的数据的。(Vue的实例对象可以看成是最底层的组件,组件的原型就是Vue对象,直接将Vue实例当做父组件,并且其中包含子组件)
开发中一些数据确实需要从上层传递到下层,当我们从服务器请求到了很多的数据,一部分数据,并非是我们整个页面的大组件来展示的,而是需要下面的子组件进行展示。并不会让子组件再次发送一个网络请求,而是直接让大组件(父组件)将数据传递给小组(子组件)
父子组件通信:
通过props向子组件传递数据
通过事件event向父组件发送(emit)消息
Vue实例和子组件的通信和父组件和子组件的通信过程是一样的
注意:props中的命名,在props中属性的名称为驼峰式命名,在调用组件的时候需要将大写字母改为小写字母并在前面加-,eg:
cMovies'>c-movies
1 | <div id="app"> |
5.5.1 父组件->子组件通信(props)
props的基本使用
- 选项props来声明需要从父级接收到的数据,props是Vue对象options中的属性
1 | Vue.component('cpn',{ |
- props的值有两种方式:
- 方式一:字符串数组,数组中的字符串就是传递时的名称。
- 方式二:对象,对象可以设置传递时的类型(此时可以对数据进行类型验证)
1 | <div id="app"> |
props的数据验证(props数据是对象情况)
验证数据类型
类型的两种写法:
- 单个类型:
属性名:类型名称
,eg:cmessage:String
- 多个数据类型:
属性名:[类型名称1,类型名称2,...]
,eg:cmessage:[String,Boolean]
- 单个类型:
验证支持的数据类型
- String
- Number
- Boolean
- Array
- Object
- Date
- Function
- Symbol
- 自定义的数据类型
当props中的属性名的验证不仅仅是数据类型还有其他类型验证时,可以属性名后面跟对象
1 | cmessage:{ |
其他数据类型验证
required:true/false,是否是必填字段
default:设置默认值,两种数据类型
- 字符串类型:
- 对象/数组类型:两种版本方式
- vue2.5版本一下:
default:[]/{}
- vue2.5版本以上:default是一个函数,
default:function(){return []/{}}
- vue2.5版本一下:
validator自定义验证
1
2
3
4validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}- 注意那些 prop 会在一个组件实例创建之前进行验证,所以实例的 property (如
data
、computed
等) 在default
或validator
函数中是不可用的。
- 注意那些 prop 会在一个组件实例创建之前进行验证,所以实例的 property (如
5.5.2 子组件->父组件通信(emit 自定义event)
子组件->父组件传递消息:需要使用自定义事件的方式
自定义事件的时期:
- 当子组件需要向父组件传递数据时,就要用到自定义事件了。
- 我们之前学习的v-on不仅仅可以用于监听DOM事件,也可以用于组件间的自定义事件。
自定义事件的流程:
在子组件中,通过$emit()来触发事件。
在父组件中,通过v-on来监听子组件事件。
代码
1 | <!-- 父组件模板 --> |
- 示意图:
- 结果:
5.5.3 兄弟组件通信(事件总线EventBus)
兄弟之间传递数据需要借助于事件中心,通过事件中心传递数据
提供事件中心 var hub = new Vue()
传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据)
接收数据方,通过mounted(){} 钩子中 触发hub.$on()方法名
销毁事件 通过hub.$off()方法名销毁之后无法进行传递数据
1 | <div id="app"> |
5.4.4 父子组件通信案例(结合双向绑定)
- 案例需求:
- 父组件的值绑定在子组件显示
- 子组件中有input,通过子组件中的input中输入的内容修改父组件的中data的值
- num1始终是num2中值的1/100倍
通过v-on:input和v-bind:value实现
1 | <!-- 父模板 --> |
小结:
- 父->子通信-父模板中:组件的动态绑定格式
:子组件名称='父组件名称'
- 子->父通信-父模板中:组件的动态绑定格式
@子组件自定义事件名称='父组件函数操作'
- 子组件中发送自定义事件格式:
this.$emit('自定义事件名称', 发送的值)
1
this.$emit('num2change', this.dnumber2)
- 通过子组件修改父组件的值时,不要使用props中的值进行通信,通过子组件中的data进行修改
1
2
3
4
5
6data() {
// 在数据中修改子元素->父元素数据的绑定
return {
dnumber1: this.number1,
dnumber2: this.number2
}- 这里的实现没有通过v-model实现,通过他的本质v-on:input和v-bind:value实现的进行实现
- 需求3
1
2
3
4
5
6
7
8
9
10
11numInput1(event) {
// 将input中输入的值赋值给dnumber
this.dnumber1 = event.target.value
// 让父组件可以一起修改发送自定义事件
this.$emit('num1change', this.dnumber1);
// 修改num2中的值是num1的100倍
this.dnumber2 = this.dnumber1 * 100;
// 发送
this.$emit('num2change', this.dnumber2);
},- 父->子通信-父模板中:组件的动态绑定格式
通过v-model和watch实现
watch是vue对象options中的属性,
类型:
{ [key: string]: string | Function | Object | Array }
详细:
一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。Vue 实例将会在实例化时调用
$watch()
,遍历 watch 对象的每一个 property。vm.$watch()
代码
1 | 改变的代码: |
5.5 组件访问
- 父组件直接访问子组件,子组件直接访问父组件,这里是直接获取组件对象的访问方式
父组件访问子组件:
- 两种方式:
- $children
- $ref
$children
- 返回的是一个子组件数组
- 每个组件中包含如下的属性

$children缺陷
通过$children访问子组件时,是一个数组类型,访问其中的子组件必须通过索引值。
但是当子组件过多,我们需要拿到其中一个时,往往不能确定它的索引值,甚至还可能会发生变化。
有时候,我们想明确获取其中一个特定的组件,这个时候就可以使用$refs
$refs
1 | <cpn ref='bb'></cpn> |
- 使用:
- $refs和ref指令通常是一起使用的。
- 首先,我们通过ref给某一个子组件绑定一个特定的ID。
- 其次,通过this.$refs.ID就可以访问到该组件了。
- 返回值是一个对象,返回的对象包含了添加ref属性的子组件的键值对,获取对应的子组件通过
vue对象.$refs.aaa
,默认情况返回的是一个空的数组
- 小结
- 一般使用refs
子组件访问父组件
1 | console.log(this.$parent); |
- $paren
- 返回的直接是父组件的对象,如果父组件是Vue对象,则返回的就是Vue对象
小结
尽管在Vue开发中,我们允许通过$parent来访问父组件,但是在真实开发中尽量不要这样做。
子组件应该尽量避免直接访问父组件的数据,因为这样耦合度太高了。
如果我们将子组件放在另外一个组件之内,很可能该父组件没有对应的属性,往往会引起问题。
另外,更不好做的是通过$parent直接修改父组件的状态,那么父组件中的状态将变得飘忽不定,很不利于我的调试和维护。
$root返回的是根元素,即Vue对象
5.6 非父子组件通信
非父子组件关系包括多个层级的组件,也包括兄弟组件的关系
在Vue1.x的时候,可以通过$dispatch和$broadcast完成
$dispatch用于向上级派发事件
$broadcast用于向下级广播事件
在Vue2.x中,有一种方案是通过中央事件总线,也就是一个中介来完成。
但是这种方案和直接使用Vuex的状态管理方案还是逊色很多。
并且Vuex提供了更多好用的功能,所以这里我们暂且不讨论这种方案,后续我们专门学习Vuex的状态管理。
5.7 插槽slot
- 学习插槽的原因:
- 插槽slot用在组件封装中,封装过程中抽取共性,保留不同
- 最好的封装方式就是将共性抽取到组件中,将不同暴露为插槽
- 一旦我们预留了插槽,就可以让使用者根据自己的需求,决定插槽中插入什么内容
5.7.1 slot的基本使用
插槽在使用过程中通过在子组件的的标签中插入内容来替换掉插槽slot中的数据
插槽的基本使用
<slot></slot>
插槽的默认值
<slot>button</slot>
如果有多个值, 同时放入到组件进行替换时, 一起作为替换元素
子组件cpn中的数据(不指定slot属性名称情况下)默认会替换掉所有的
<slot></slot>
代码:
1 | <div id="app"> |
5.7.2 具名插槽
- 插槽中通过在slot标签中插入name属性来来区分不同的插槽
- 使用:
<slot name='value'></slot>
<cpn slot='value'>插入插槽中的内容<cpn>
- 代码
1 | <div id="app"> |
5.7.3 作用域插槽
编译作用域
- 父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译
作用域插槽:
父组件替换插槽的标签,但是内容由子组件来提供
格式:
- 插槽中自定义属性<slot :自定义属性名称=’子组件data中的属性’>
1
<slot :data='pLanguages'> </slot>
- 父组件中通过slot-scope获取插槽
- slot.data获取子组件中自定义属性的值
1
2
3
4
5
6
7
8
9<cpn>
<!-- <!--目的是获取子组件中的pLanguages--> -->
2.5.x版本以下只能通过template标签,以上的版本,div等元素也可以
<template slot-scope='slot'>
<!-- 听过slot.data获取子组件中的自定义的data属性值 -->
<span v-for='item in slot.data'>{{item}}-</span>
<!-- <span>{{slot.data.join('-')}}</span> -->
</template>
</cpn>完整代码
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<div id="app">
<!-- 父组件通过不同的方式显示子组件的内容,这里用到作用域插槽 -->
<!-- 1.使用默认的方式显示 -->
<cpn> </cpn>
<!-- 2.父组件自定义的方式:
- 插槽中自定义属性<slot :自定义属性名称='子组件data中的属性'></slot>
- 子组件中通过slot-scope获取插槽
- slot.data获取子组件中自定义属性的值
-->
<cpn>
<!-- <!--目的是获取子组件中的pLanguages--> -->
2.5.x版本以下只能通过template标签,以上的版本,div等元素也可以
<template slot-scope='slot'>
<!-- 听过slot.data获取子组件中的自定义的data属性值 -->
<span v-for='item in slot.data'>{{item}}-</span>
<!-- <span>{{slot.data.join('-')}}</span> -->
</template>
</cpn>
</div>
<template id="cpn">
<div>
<h2>我是子组件</h2>
<slot :data='pLanguages'>
<!-- 设置默认的插槽内容 -->
<ul>
<li v-for="item in pLanguages">{{item}}</li>
</ul>
</slot>
</div>
实践小结
- 在使用自定义组件的时候,可以是单标签可以双标签,双标签中没有内容的时候使用单标签
本文链接: https://sparkparis.github.io/2020/05/07/Vue%E7%AC%94%E8%AE%B02/
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!