一. 了解 Vue.mixin
介绍:该 API 就是为了给组件提供一些可复用的属性,以此来扩展组件的功能。
二. 实际应用
在了解它实现原理之前,我们先来看下它的实际应用。
假如我们需要给所有组件都添加一个 token
属性,并且在 beforeCreate
中执行某些相同的操作,我们可以执行如下操作,这样一来所有组件中都默认拥有这些属性和方法。(下边实例为全局注册,局部注册比较容易理解,可自己查看文档)
Vue.mixin({
data(){
return {
token: 'token'
}
},
beforeCreate(){
console.log('执行方法')
}
})
我们将组件实例打印一下,可以看到 token
已经被添加到组件中。
这里只是举了一个小例子,具体使用需要结合场景去用,但是它确实可以将组件中重复的代码抽离出来,实现一个很好的复用。
三. mixin 是如何做到给所有组件都同时添加属性的呢?
我们来分析一下,所有的子组件都来自于 VueComponent
构造函数的实例,而Vuecomponent
又继承至根组件Vue
的实例,并且在继承时使用mergeOptions
(后面会讲解)方法将VueComponent
和Vue
中的options
进行合并,所以想给每个组件都实现混入,只要给Vue
的options
中添加要混入的对象即可。那我们来看看 Vue中是如何实现的吧。
1. Vue.mixin 函数的实现
该方法的逻辑很简单,就是将 Vue
自身的options
和传入的options
作为参数调用 mergeOptions
方法,并将返回值重新写入Vue的options
。
Vue.mixin = function (mixin: Object) {
this.options = mergeOptions(this.options, mixin)
return this
}
2. mergeOptions 函数的实现
以下是实现mergeOption
的源码,实现比较容易理解,难懂的地方我写了上注释。
function mergeOptions (
parent: Object,
child: Object,
vm?: Component,
uid,
isRoot
): Object {
normalizeProps(child, vm, uid) // 将child 中的 props属性规范化,props我们在使用时有两种写法,数组和对象的形式,规范之后统一变为对象的形式。
normalizeInject(child, vm, uid) // 同上
normalizeDirectives(child, uid)
if (!child._base) {
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm)
}
if (child.mixins) {
for (let i = 0, l = child.mixins.length; i < l; i++) {
parent = mergeOptions(parent, child.mixins[i], vm)
}
}
}
const options = {}
let key
for (key in parent) {
mergeField(key)
}
for (key in child) {
if (!hasOwn(parent, key)) {
mergeField(key)
}
}
function mergeField (key) { // 该方法是真正实现合并的地方,这里用了一个策略模式,对不同的属性,使用与其对应的方法进行合并。将父子options中的各个属性进行合并。
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key)
}
return options
}
看了源码可能还是有点懵,我来举个例子。其实就是将两个options
parentOptions = {
// 当然可能还有其他属性,如props,生命周期hook等等
data(){
return{
name: '石头山',
age: '12'
}
}
}
childOptions = {
data(){
return{
name: '前端开发',
gender: '男'
}
}
}
// 合并之后的 options
mergeOptions = {
data(){
return {
name: '石头山',
age: '12',
gender: '男'
}
}
}
2. 为每一个实例上合并 mixin 的属性
看了上边代码,小伙伴们有没有想提出这么一个问题,上边不是只给 Vue
的 options
上进行了合并吗,但是组件的实例 vm
上是怎么拥有这些属性的呢?这里就需要明白两个点了,请往下看。
- Vue.extend(VueComponent 的由来)
这里可以看到,VueComponent
它是继承至Vue
,并且合并了Vue
的options
,因此,VueComponent.options
便有了我们使用mixin
混入进来的对象属性。
// 详细代码在讲 extend API 时讲,这里只讲合并
Vue.extend = function (extendOptions: Object): Function {
const Sub = function VueComponent (options) {
this._init(options)
}
Sub.options = mergeOptions(
Super.options,
extendOptions
)
}
- this._init()
该方法是执行初始化时调用的方法,也是Vue
执行的入口,实现原理看代码中的注释
Vue.prototype._init = function (options?: Object) {
// 省略详细代码
if (options && options._isComponent) { // 如果该实例是组件实例执行
initInternalComponent(vm, options) // 该方法往下看
} else { // 根实例执行
vm.$options = mergeOptions( // 根实例vm.$options是合并了Vue和我们传入的options
resolveConstructorOptions(vm.constructor),
options || {},
vm,
uid,
true
)
}
}
function initInternalComponent (vm: Component, options: InternalComponentOptions) {
const opts = vm.$options = Object.create(vm.constructor.options) // 组件实例vm.$options 是将原型链指向了 VueComponent.options
}
四. 结尾
通过全局API Vue.mixin
源码的学习,我们可以了解作者的构建思路,更好的把握对该API的使用,并且学习编程代码优秀的写法和设计模式,在这个API
中使用了策略模式
,也展现了原型链
实现继承的巧妙。