1、Vue组件是怎么来的?
我们都知道Vue组件是一个可复用的Vue实例,那么它是怎么来的呢,通过官方文档我们看到Vue.extend、Vue.component这两个全局API。
- Vue.extend是通过Vue的基本构造器创建一个子类,接收一个参数为:
options{Object} - Vue.component是通过Vue.extend子类来创建一个自己命名的组件,接收两个参数为:
组件名{string}
子类{Function} | options{Object}
让我们把眼光放在Vue.component方法中的第二个参数上的说明,当参数是一个Function时,这时候传入的就是一个由 Vue.extend 创建的一个子类,而Vue.component实际就是返回一个由Vue.extend创建的子类new出来的一个实例,当然如果参数不是一个Function而是一个Object时,则会自己调用Vue.extend创建一个同options同样属性方法的一个子类,然后再返回一个new出来的实例出来。
所以从上面的可知,Vue组件的就是一个由Vue子类new出来的一个子实例对象。这也就是组件为什么会和Vue根实例一样都有生命周期钩子函数、data、methods等属性和方法的原因了。
2、Vue组件实例与Vue根实例的区别
从Vue组件的定义(是一个可复用的Vue实例)就可以知道Vue组件的最明显特征就是可复用,既然是复用的话,那么el与data这两个属性在组件中要不就不适用要不就需要经过特殊处理才行。
el属性是挂载页面上已存在的 具体的DOM元素 ,我们试想一下如果组件中使用el挂载DOM元素的情况下,当页面中这个组件存在多个的时候,那么就相当于多个组件绑定了同一个DOM元素,这样不止在组件的使用上会有冲突,同时也会失去复用的特征,所以在组件中不能使用el属性,而使用template属性来挂载一个动态生成的DOM节点,这样每个组件所挂载的DOM都是独一无二的互不影响的。
data属性是在组件创建过程中Vue.extend子类中赋予的属性,所以当这个组件存在多个时,这些组件实例中的data其实都是指向的同一个data对象,这也和我们复用的初衷相违背,所以在创建组件的options里不能直接给data属性赋值一个对象,而是通过一个函数返回一个对象。如:
{
data: () => ({
msg: "Hello wrod!"
})
}
3、全局组件、局部组件(子组件)
在前面组件的由来中已经提到过Vue组件是通过Vue.component来创建的一个Vue子类实例,理解了这一过程那创建组件就很好办了。如下:
// 创建一个子类
let MyCom = Vue.extend({
template: `{{msg}}
`,
data: () => ({
msg: '我是一个组件'
})
});
根据子类创建一个组件的实例
Vue.component('my-com', MyCom),
// 创建根实例,注意:只有存在Vue根实例的场景下,并且组件必须要在Vue根实例前注册,不然会报错
new Vue({
el: "#app",
});
// 使用组件
前面也说过Vue.component还可以接收一个options参数的方式更简单快捷的创建组件。如:
// 使用options的方式创建组件
Vue.component('my-com', {
template: `{{msg}}
`,
data: () => ({
msg: '我是一个组件'
})
}),
// 创建Vue根实例
new Vue({
el: "#app",
});
上述这种组件,我们称为全局组件,它们可以在任何一个Vue实例中使用,并且互不统属。
但是这种全局组件虽然能达到复用的效果,但还是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。
所在这种情况就有了局部组件的产生,局部组件的创建方法如下:
// 子组件:子菜单组件的options
const SubMenuCom = {
template: `我是一个子菜单组件`
}
// 全局组件:菜单组件的options
const MenuCom = {
template: `
我是菜单组件
`,
components: {
SubMenuCom
}
}
// 创建Vue根实例
new Vue({
el: "#app",
components: {
MenuCom
}
});
通过上面的例子,我们可以得出:
- 在组件中的components属性来注册的组件就是局部组件
- 在Vue.component中注册的组件就是全局组件
局部组件在哪个组件中注册的就只能在哪个组件中使用,同样全局组件可以直接在任何地方使用,可以理解成全局组件默认就是根实例中注册的,所以在任何位置都可以使用。
4、组件之间传值
1、props传参:通过在使用子组件时为其添加属性或动态属性,然后在注册子组件中使用props属性来接收这些属性来实现向子组件传值这一过程。
// 在子组件中使用props属性来接收count,title传过来的值
const SubMenuCom = {
template: `
{{msg}}
{{title}}
{{count}}
`,
props: ['count', 'title'],
data: () => ({
msg: "我是子组件"
})
}
// 在使用子组件时为其添加一个属性为title和一个动态属性为count
const MenuCom = {
template: `
`,
data: () => ({
n: 0
}),
components: {
SubMenuCom
}
}
// 创建Vue根实例
new Vue({
el: "#app",
components: {
MenuCom
}
});
注意:因为Vue是一个响应式的框架,所以在上面的例子中,在使用子组件时定义了一个动态属性count,而count的值 又是绑定的data属性中的n,所以当在父组件中改变这个n的值的时候,这个值也会响应到动态属性count中,从而也会影响到子组件中的count。
2、事件传参:通过子组件触发父组件的事件向父组件传递参数。
// 在子组件中使用使用$emit来调用父组件传过来的事件
const SubMenuCom = {
template: `
{{count}}
`,
methods: {
addCount() {
// 在子组件的事件中通过this.$emit来调用父组件的自定义事件
this.$emit('addCount', 5);
}
},
props: ['count', 'title']
}
// 向子组件传递一个count属性值为n,一个addCount自定义事件执行方法为add
const MenuCom = {
template: `
`,
data: () => ({
n: 0
}),
methods: {
add(v) {
this.n += v;
}
},
components: {
SubMenuCom
}
}
// 创建Vue根实例
new Vue({
el: "#app",
components: {
MenuCom
}
});
同样如果在父组件中的自定义事件不是一个methods方法,而是使用表达来处理时,那么接收子组件传过来的参数就要使用event只能接收一个参数),所以使用事件来互相传参可以简写成以下形式:
// 子组件
const MyIntput = {
template: `
`
}
// 父组件(当前使用根实例作为父组件)
const vm = new Vue({
el: "#app",
components: {
MyIntput
},
data: {
n: 0
}
});
3、v-model传参:通过使用v-model双向数据绑定实现父子组件间互相传值,但是有一点只能绑定value属性的值,同在表单元素上使用v-model一样。
至于为什么在组件上只能绑定value属性,我们可以简单的模拟一下这个原理。
首先按上面的思路我们如果需要达到这种效果的话,那就给子组件传一个value的动态属性以及一个表单输入的事件,再由子组件使用$emit的方式来操作父组件给value属性的值达到数据双向响应的效果,如下:
// 子组件
const MyIntput = {
props: ['value'],
template: `
`
}
// 父组件(当前使用根实例作为父组件)
const vm = new Vue({
el: "#app",
components: {
MyIntput
},
data: {
searchText: "请输入内容"
}
});
然后我们来直接使用v-model绑定组件value的值 ,就不需要再给子组件传动态属性value以及事件了,因为v-model会自己传,所以就可以把双向绑定子组件属性value可以简写成:
//使用简写
//不使用简写
当然子组件里的写法还是不变,所以在组件间使用v-model传参,就是其实就是组件事件传参的value属性的简写。
4、兄弟组件传值(子传父、父再传另一子)
就是根据上面的方法,子组件先把通过emit事件传参的综合运用,只要把父组件中的接收子组件的值和传给另一子组件的值使用同一个data中的变量即可。
5、通用引用一个空vue实例作为媒介调用以及监听事件(网上叫法为:bus总线传值的使用)
上面提到过兄弟组件传值的方法,就是子传父,再由父传子的这种方法。但是发现这种方式过于麻烦,而且还不利于维护,所以就还有另一种,使用一个空的vue实例作为一个媒介分别在兄弟组件中使用on来调用和监听事件来传值。
为了方便就在vue-cli中举例了,首先创建一个bus.js导出一个空vue实例
import Vue from 'vue'
export default new Vue;
然后在创建ComA组件
创建ComB组件
{{n}}
ComA与ComB使用兄弟组件使用
在上面的例子中ComA组件引入一个为bus的vue空实例,再通过点击事件让bus来调用一个自定义事件,然后再ComB组件中同样也将这个bus引入并监听这个自定义事件,来获取参数从而使组件ComA传值到ComB。
6、使用vuex(放到专门的vuex里去总结)
5、动态组件
动态组件一般是应用在不同组件间切换,或是在根据数据格式判定来呈现不一样的方式时,使用vue内置的commponent标签来实现,如:
is属性里的值是要显示的组件名或一个存组件名的变量
动态组件会重新渲染当前生效的组件,所以并不会保持每个组件的操作状态,但有时候也需要每次切换组件的时候能保持每个组件状态,从而不需要重新渲染,这就需要用到Vue的另一人内置标签keep-alive,将非活动组件暂存起来
6、异步组件
往往在大型应用中,应用分割成小的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。例如:
new Vue({
components: {
'my-component': () => import('./my-async-component')
}
})