一、什么是组件? 什么是组件化?
组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用 is
特性进行了扩展的原生 HTML 元素。
简单的说: 组件就是把一个很大的界面拆分为多个小的界面, 每一个小的界面就是一个组件
将大界面拆分成小界面就是组件化
组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界面都可以抽象为一个组件树:
组件化的好处
可以简化Vue实例的代码.
可以提高代码复用性
二、注册组件
全局注册
所有实例都能用全局组件。
1. 创建组件构造器
通过 全局API:Vue.extend()
参数:{Object} options
用法:使用基础 Vue 构造器,创建一个“子类”。参数是一个 包含组件选项的对象。
let Profile = Vue.extend({
// 注意: 在创建组件指定组件的模板的时候, 模板只能有一个根元素
template: `
我是描述信息
`
});
2. 注册已经创建好的组件
Vue.component('my-component', Profile);
3. 使用注册好的组件
创建组件的简化方式
- 在注册组件的时候, 除了传入一个组件构造器以外, 还可以直接传入一个 对象
Vue.component('my-component', {
template: `
我是描述信息
`
});
- 在编写组件模板的时候, 除了可以在 字符串模板 中编写以外, 还可以像
art-template
一样在 script 中编写
- 在编写组件模板的时候, 除了可以在 script 中编写以外, vue还专门提供了一个编写模板的标签
template
我是描述信息
上面两种编写模板的方式在创建的时候一定要记得加上 id
, 在使用的时候也要加上 id名称
Vue.component('my-component', {
template: '#info'
});
局部注册
我们也可以在实例选项中注册局部组件,这样组件只能在这个实例中使用.
可以通过某个 Vue 实例/组件的实例选项 components
注册仅在其作用域中可用的组件:
new Vue({
// ...
components: {
'my-component': {
template: '#info'
}
}
});
自定义全局组件特点:
在任何一个Vue实例控制的区域中都可以使用
自定义局部组件特点:
只能在自定义的那个Vue实例控制的区域中才可以使用
三、组件中的data和methods
Vue实例控制的区域相当于一个大的组件, 在大组件中我们可以使用data
和methods
而我们自定义的组件也是一个组件, 所以在自定义的组件中也能使用data
和methods
1. Vue中使用data和methods
{{vueMsg}}
new Vue({
el: '#app',
methods: {
vueFn(){
alert('vue-Fn');
}
},
data: {
vueMsg: 'vue-Msg'
}
});
2. 自定义组件中使用data和methods
在自定义组件中不能像在vue实例中一样直接使用data,而是必须通过返回函数的方式来使用data。
{{myMsg}}
Vue.component('my-component', {
template: '#info',
methods: {
myFn(){
alert('my-Fn');
}
},
data: function () {
return {
myMsg: 'my-Msg'
}
}
});
自定义组件中的data为什么是一个函数
因为自定义组件可以复用, 为了保证复用时每个组件的数据都是独立的, 所以必须是一个函数
看下面这个例子:
// HTML
{{counter}}
// JS
Vue.component('my-component', {
template: '#info',
data: function () {
return {
counter: 0
}
},
methods: {
add(){
this.counter++;
}
}
});
new Vue({
el: '#app',
});
运行结果: 点击按钮的时候只有自己按钮下的数据会加1
组件中的data如果不是通过函数返回的, 那么多个组件就会共用一份数据, 就会导致数据混乱。
组件中的data如果是通过函数返回的, 那么每创建一个新的组件, 都会调用一次这个方法,将这个方法返回的数据和当前创建的组件绑定在一起, 这样就有效的避免了数据混乱。
如果 Vue 没有这条规则,点击一个按钮就会像影响到其它所有实例:那么上面的例子中的数据就会一起加1;
四、组件切换
1. 通过 v-if / v-else
对于普通的元素我们可以通过v-if来实现切换,对于组件我们也可以通过v-if来实现切换。
因为组件的本质就是一个自定义元素。
// HTML
我是info1
我是info2
2. 通过动态组件
通过v-if/v-else-if/v-else确实能够切换组件,但是在Vue中切换组件还有一种更专业的方式:
component我们称之为动态组件, 也就是你让我显示谁我就显示谁
通过使用
元素,动态地绑定到它的 is 特性
我是info1
我是info2
为什么可以通过v-if切换还要有component
因为component
可以配合keep-alive
来保存被隐藏组件隐藏之前的状态, 而v-if会重新渲染页面, 所以不能保存之前的状态
包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和
相似,
是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在父组件链中
我是info1
这里可以记住复选框的选中状态
如果我们需要频繁的切换页面,每次都是在组件的创建和销毁的状态间切换,这无疑增大了性能的开销。
这个时候我们也可以使用Vue提供了动态组件的 缓存。keep-alive
会在切换组件的时候缓存当前组件的状态,等到再次进入这个组件,不需要重新创建组件,只需要从前面的缓存中读取并渲染。
五、组件动画
给组件添加动画和过去给元素添加动画一样。
如果是单个组件就使用transition
,如果是多个组件就使用transition-group
。
// HTML
我是info1
我是info2
// CSS
.v-enter{
opacity: 0;
margin-left: 500px;
}
.v-enter-to{
opacity: 1;
}
.v-enter-active{
transition: all 3s;
}
.v-leave{
opacity: 1;
}
.v-leave-to{
opacity: 0;
}
.v-leave-active{
transition: all 3s;
margin-left: 500px;
}
// JS
效果:
在这个案例中可以发现一个问题: 两个的话是同时执行的.
默认情况下进入动画和离开动画是同时执行的, 如果想一个做完之后再做另一个, 需要指定动画的过渡模式.
过渡模式
过渡模式常常配合多个元素或者多个组件切换时使用,有如下两种模式:
-
in-out
:新元素先进行过渡,完成之后当前元素过渡离开。(默认为该模式) -
out-in
:当前元素先过渡离开,离开完成后新元素过渡进入。
所以可以把上面的代码改造一下, 就可以让一个元素先出去, 另一个元素再进来
六、父子组件
在一个组件中又定义了其它组件就是父子组件。
其实局部组件就是最简单的父子组件, 因为我们可以把Vue实例看做是一个大组件。
我们在Vue实例中定义了局部组件, 就相当于在大组件里面定义了小组件, 所以局部组件就是最简单的父子组件
1. 如何定义父子组件
前面讲过, 自定义组件中可以使用data
, 可以使用methods
. 当然自定义组件中也可以使用components
,
所以我们也可以在自定义组件中再定义其它组件。
- 在全局父组件中定义子组件
Vue.component('father', {
template: '#father',
components: {
'son': {
template: '#son'
}
}
});
- 在局部父组件中定义子组件
new Vue({
el: '#app',
components: {
'father': {
template: '#father',
components: {
'son': {
template: '#son'
}
}
}
}
});
- 父子组件的使用
把自定义子组件放到自定义父组件中, 把自定义父组件放到Vue组件中
我是父组件
我是子组件
2. 父子组件数据传递
在Vue中子组件是不能访问父组件的数据的,如果子组件想要访问父组件的数据, 必须通过父组件传递。
如何传递数据
- 在父组件中通过
v-bind
传递数据
传递格式: v-bind:自定义接收名称 = "要传递数据"
{{msg}}
- 在子组件中通过
props
接收数据
接收格式: props: ["自定义接收名称"]
//...
components: {
'son': {
template: '#son',
// 这里通过parentmsg接收了父组件传递过来的数据
props: ['parentmsg']
}
}
如何使用数据
- 子组件使用父组件传递的数据
{{parentmsg}}
3. 父子组件方法传递
在Vue中子组件是不能访问父组件的方法的,如果子组件想要访问父组件的方法, 必须通过父组件传递
如何传递方法
- 在父组件中通过
v-on
传递方法
传递格式: v-on:自定义接收名称 = "要传递方法"
- 在子组件中自定义一个方法
- 在自定义方法中通过
this.$emit('自定义接收名称');
触发传递过来的方法
components: {
'son': {
template: '#son',
methods: {
sonFn(){
this.$emit('parentsay');
}
}
}
}
和传递数据不同, 如果传递的是方法, 那么在子组件中不需要接收。
但是需要在子组件中自定义一个方法, 直接使用自定义的方法.
并且还需要在子组件自定义的方法中通过
this.$emit("自定义接收的名称")
的方法来触发父组件传递过来的方法
$emit( eventName, […args] ) 触发事件
- {string} eventName 需要调用的函数名称
- [...args] 给调用的函数传递的参数
触发当前实例上的事件。附加参数都会传给监听器回调。
所以子组件可以通过这个方法给父组件传递参数.
components: {
'son': {
template: '#son',
methods: {
sonFn(){
this.$emit('parentsay', 'son');
}
}
}
}
父组件接收参数:
methods: {
say(data){
console.log(data);
}
}
4. 数据和方法的多级传递
在Vue中如果儿子想使用爷爷的数据, 必须一层一层往下传递
在Vue中如果儿子想使用爷爷的方法, 必须一层一层往下传递
七、组件中的命名
1. 注册组件的时候使用了"驼峰命名", 那么在使用时需要转换成"短横线分隔命名"
例如: 注册时: myFather -> 使用时: my-father
2. 在传递参数的时候如果想使用"驼峰名称", 那么就必须写"短横线分隔命名"
例如: 传递时: parent-msg="msg" -> 接收时: props: ["parentMsg"]
3. 在传递参数的时候如果想使用"驼峰名称", 那么就必须写"短横线分隔命名"
例如: @parent-say="say" -> this.$emit("parent-say");
八、插槽
默认情况下使用子组件时,在子组件中编写的元素是不会被渲染的
如果子组件中有部分内容是使用时才确定的, 那么我们就可以使用插槽
插槽就是在子组件中放一个"坑", 以后由父组件来"填"。
比如在下面这个例子中, 没有使用插槽的话父组件在
内编写的内容是无效的
默认情况下是不能在使用子组件的时候, 给子组件动态的添加内容的
如果想在使用子组件的时候, 给子组件动态的添加内容, 那么就必须使用插槽
1. 匿名插槽
没有名字的插槽, 会利用使用时指定的内容替换整个插槽
这里的slot标签
就是插槽, 插槽其实就是一个坑。只要有了这个坑, 那么以后使用者就可以根据自己的需要来填这个坑。
我是头部
我是默认的内容
我是底部
插槽可以指定默认数据, 如果使用者没有填这个坑, 那么就会显示默认数据。
如果使用者填了这个坑, 那么就会利用使用者坑的内容替换整个插槽。
例如: 这个father组件在使用son组件的时候填了这个坑,那么就会用父组件坑的内容覆盖掉整个插槽.
所以最后的效果是:
我是追加的内容
匿名插槽的特点:
有多少个匿名插槽, 填充的数据就会被拷贝几份
我是头部
我是默认的内容
我是默认的内容
我是底部
效果图:
虽然我们可以指定多个匿名插槽, 但是推荐只写一个匿名插槽
2. 具名插槽
默认情况下有多少个匿名插槽, 我们填充的数据就会被拷贝多少份,这导致了所有插槽中填充的内容都是一样的。
那么如果我们想给不同的插槽中填充不同的内容怎么办呢?
这个时候就可以使用具名插槽
具名插槽的使用
- 通过插槽的
name
属性给插槽指定名称
我是头部
我是one默认的内容
我是two默认的内容
我是底部
- 在使用时可以通过
slot="name"
方式, 指定当前内容用于替换哪一个插槽
默认情况下填充的内容是不会被填充到具名插槽中的,
只有给填充的内容指定了要填充到哪一个具名插槽之后,
才会将填充的内容填充到具名插槽中
我是追加的内容one
我是追加的内容two
3. v-slot指令
v-slot
指令是Vue2.6中用于替代slot
属性的一个指令
在Vue2.6之前, 我们通过slot
属性告诉Vue当前内容填充到哪一个具名插槽
从Vue2.6开始, 我们通过v-slot
指令告诉Vue当前内容填充到哪一个具名插槽
格式: v-slot:插槽名称
简写: #插槽名称
注意: v-slot指令只能用在template标签上
例如:
我是追加的内容one
我是追加的内容one
我是追加的内容two
我是追加的内容two
4. 作用域插槽
作用域插槽就是带数据的插槽, 就是让父组件在填充子组件插槽内容时也能使用子组件的数据
如何使用作用域插槽
- 在
slot
中通过v-bind:数据名称="数据名称"
方式暴露数据
我是头部
我是底部
- 在父组件中通过
接收数据
- 在父组件的
中通过 作用域名称.数据名称 方式使用数据
{{sonMsg.names}}
作用域插槽的应用场景:
子组件提供数据, 父组件决定如何渲染
5. v-slot 指令代替 slot-scope
在 2.6.0 中,我们为具名插槽和作用域插槽引入了一个新的统一的语法 (即 v-slot 指令)。
它取代了 slot 和 slot-scope
也就是说我们除了可以通过 v-slot
指令告诉Vue内容要填充到哪一个具名插槽中,还可以通过v-slot
指令告诉Vue如何接收作用域插槽暴露的数据
格式: v-slot:插槽名称="作用域名称"
简写: #插槽名称="作用域名称"
{{sonMsg.names}}