最近,新项目架构搭建在扩展组件的场景中:图表使用了extends方式,而公共业务server和view之间使用了mixins方式。对于二者的选择,我们通常会解释为extends的优先级高于mixins,但其真实的差异是由于其合并策略不同或者说在合并策略中执行的顺序不同导致的 – ​​源码​

下述,简单阐述Vue中涉及到组件扩展的相关API及自定义合并策略等相关内容

定义公共基础的组件配置模板,下述示例使用

const BaseComponent = {
name: 'BaseComponent',
template: `<div>{{message}}</div>`,
data () {
return {
message: 'My is BaseComponent!'
}
},
created () {
console.log(`BaseComponent ${this.message}`)
}
}

Vue.extend( options )

返回的是一个“扩展实例构造器”,也就是一个预设了部分选项的 Vue 实例构造器,方便创建可复用的组件。参数是一个包含组件选项的对象,其中data必须为函数。

<div id="app"></div>
<script>
const HelloWorld = Vue.extend(BaseComponent)
// 创建 HelloWorld 实例,并挂载到一个元素上。
new HelloWorld().$mount('#app')
</script>

​Vue.extend​​​ 是构造一个组件的语法器。可以作用到 ​​Vue.component​​ 中;也可以作用到 vue 实例或者某个组件中的 components 属性。

示例1:作用于 Vue.component 中,其为全局注册,也就是说它们在注册之后可以用在任何新创建的 Vue 根实例 ( ​​new Vue​​ ) 的模板中

Vue.component('myBase', BaseComponent)

示例2: 作用于某组件中

const base = Vue.extend(BaseComponent)

上述两者都可以生效:

<div id="app">
<my-base></my-base>
<base-component></base-component>
</div>
<script>
new Vue({
el: '#app',
components: {
baseComponent: base
}
})
</script>

​Vue.component()​​​ & ​​Vue.extend(options)​​ 区别:

前者创建的是组件(组件是可复用的 Vue 实例),后者创建的是组件构造器。​​Vue.component('my-component', { /* ... */ })​​ 注册组件,传入一个选项对象 (自动调用 Vue.extend)

// 注册组件,传入一个扩展过的构造器
Vue.component('my-component', Vue.extend({ /* ... */ }))
// 注册组件,传入一个选项对象 (自动调用 Vue.extend)
Vue.component('my-component', { /* ... */ })
// 获取已注册的组件 (始终返回构造器)
var MyComponent = Vue.component('my-component')

Vue.mixin( mixin )

全局注册一个混入,影响注册之后所有创建的每个 Vue 实例。使用恰当时,可以为自定义对象注入处理逻辑。但不推荐在应用代码中使用。因为会影响到每个单独创建的 Vue 实例 (包括第三方模板)!

Vue.mixin({
created () {
var myOption = this.$options.myOption
if (myOption) {
console.log(myOption)
}
}
})
new Vue({
myOption: 'hello!'
})

​vm.$options​​ 用于当前 Vue 实例的初始化选项。需要在选项中包含自定义属性时会有用处。

new Vue({
customOption: 'foo',
created: function () {
console.log(this.$options.customOption) // => 'foo'
}
})

同时,其也可以获取Vue内部的一些初始选项,如在​​自动销毁的vue event Bus​​可以使用到:

// 当前调用组件的 destroyed 钩子
const destroyed = vm.$options.destroyed
// 为当前组件destroyed钩子增加销毁方法
!destroyed.includes(destroyHandler) && destroyed.push(destroyHandler)

extends

允许声明扩展另一个组件(可以是一个简单的选项对象或构造函数),而无需使用 ​​Vue.extend​​。这主要是为了便于扩展单文件组件。

const vm = new Vue({
el: '#app',
extends: BaseComponent,
data () {
return {
message: 'My is Mixins!'
}
},
created: function () {
console.log(`mixins ${this.message}`)
}
})
# DOM展示
My is Mixins!
# 控制台输出结果:
BaseComponent My is Mixins!
mixins My is Mixins!

mixins

接受一个混入对象的数组。这些混入实例对象可以像正常的实例对象一样包含选项,他们将在 ​​Vue.extend()​​ 里最终选择使用相同的选项合并逻辑合并。

const vm = new Vue({
el: '#app',
mixins: [BaseComponent],
data () {
return {
message: 'My is Mixins!'
}
},
created: function () {
console.log(`mixins ${this.message}`)
}
})

输出结果和extends一致!!!

extends和mixins的区别

let myMixin = {
data () {
return {
message: 'local mixin'
}
},
created () {
console.log('local mixin')
}
}
let myExtend = {
data () {
return {
message: 'local extend'
}
},
created () {
console.log('local extend')
}
}
new Vue({
el: '#app',
extends: myExtend,
mixins: [myMixin],
template: `<div>{{message}}</div>`,
created () {
console.log('current Vue instance')
}
})
# DOM展示
local mixin
# 控制台输出结果
Vue.mixin
local extend
local mixin
current Vue instance

Vue 组件扩展_vue

通过Vue的 ​​源码​​ 可知,extends 优先于 mixins 进行合并,所以会先执行 extends 的 created,但是对于相同的data属性,后面的会覆盖前面的!这些都是由合并策略所决定的!!!

合并策略

/**
* Default strategy.
*/
const defaultStrat = function (parentVal: any, childVal: any): any {
return childVal === undefined
? parentVal
: childVal
}

上述代码为Vue​​源码​​中关于策略合并的默认配置。子组件为主,子组件存在则使用子组件的,否则使用父组件的!!!

下述根据Vue源码,说明各个属性的合并策略,具体可以通过后面的链接查看Vue源码!

  • ​options.el​​​ 合并策略就是默认的合并策略,即以子组件的选项为主,子组件的选项不存在时,才使用父组件的。​​源码地址​
  • ​options.data​​​ 子组件没有响应的属性则使用父组件的,否则子组件的会覆盖父组件的! ​​源码地址​
  • ​options.props options.methods options.computed​​​ 先拓展的是 ​​parentVal​​​ 对象,然后再拓展 ​​childVal​​​ 对象,存在相同的 ​​childVal​​​ 会覆盖 ​​parentVal​​​ 值。​​源码地址​
  • ​options.hook​​​ 父组件和子组件都设置了钩子函数选项,那么它们会合并到一个数组里,而且父组件的钩子函数会先执行,最后返回一个合并后的数组。 ​​源码地址​
  • ​options.components options.directives options.filters​​​ 合并的策略就是返回一个合并后的新对象,新对象的自有属性全部来自 ​​childVal​​,但是通过原型链委托在了 ​​parentVal​​​ 上。事实上,和 defaultStrat 一个道理。​​源码地址​
  • ​options.watch​​​ 相同属性会做合并处理,父组件在前,子组件在后。​​源码地址​

自定义合并策略

通过查看 ​​源码​​,我们可以发现所有的策略都是依赖strats进行的,也就是基于​​config.optionMergeStrategies​​完成的。

/**
* Option overwriting strategies are functions that handle
* how to merge a parent option value and a child option
* value into the final value.
*/
const strats = config.optionMergeStrategies

Vue提供了自定义合并策略的选项的API,​​optionMergeStrategies​​ 合并策略选项分别接收在父实例和子实例上定义的该选项的值作为第一个和第二个参数,Vue 实例上下文被作为第三个参数传入。

Vue 组件扩展_vue_02


示例: 修改默认合并策略

new Vue({
el: '#app',
mixins: [{
created () {
console.log(`BaseComponent`)
}
}],
created () {
console.log('this instance')
}
})

由上述总结的默认合并策略可知,hook 会合并到一个数组中,且父组件优先执行,因此输出结果为:

BaseComponent
this instance

修改合并策略

Vue.config.optionMergeStrategies.created = function (parentVal, childVal) {
return Array.isArray(childVal) ? childVal : [childVal]
}

再次执行上述代码,可发现只输出 ​​this instance​​ 。合并策略已被修改!对于Vue内部定义的策略强烈不建议进行修改!!!