# Vue.mixin原理

# 实现原理

Vue.mixin源码位于src/core/global-api/mixin.js文件中,如下:

import { mergeOptions } from '../util/index'
// mixin主要用于options合并
export function initMixin (Vue: GlobalAPI) {
  Vue.mixin = function (mixin: Object) {
    this.options = mergeOptions(this.options, mixin)
    return this
  }
}
1
2
3
4
5
6
7
8

mixin主要做了一件事儿,就是调用mergeOptions方法,将本身的mixin对象,与this.options对象合并,并赋值给this.options对象。

mergeOptions:主要作用就是options选项的合并,包括data、computed、methods、生命周期钩子等。

举例说明:

Vue.mixin({
  a: 1,
  b: 2
});
console.log(Vue.options); // 合并后的全局Vue.options
const App = {
  template: '<div>app</div>'
};
const vm = new Vue({
  name: 'root',
	render: h => h(App)
}).$mount('#app');
console.log(vm.$options); // 当前vm的options选项
1
2
3
4
5
6
7
8
9
10
11
12
13

Vue.options值:

image-20210929100901954

vm.$options值:

注意:vm.$options值,是调用mergeOptions方法,将Vue.options与vm自身的options合并后的结果。

image-20210929100940208

# mixin作用

既然mixin可以将自身的options选项,与Vue.options合并。那么我们就可以将一些公共的方法、属性,或者生命周期通过mixin的方式,挂载Vue.options上。这样,无论是根实例,还是子组件实例,都可以通过vm.$options访问到。

  • 根组件在_init初始化时,会将Vue.options与自身options合并,并赋值给vm.$options

  • 子组件初始化之前,会调用Vue.extend方法返回一个子类Sub,该Sub类继承自父类Vue。在Vue.extend执行阶段。会调用mergeOptions,将Vue.options与子组件选项options合并到Sub.options上。当子组件调用_init时,将vm.$options通过原型链,与Sub.options关联,参考源码src/core/instance/init.js:

    // Vue.extend
    Vue.extend = function (extendOptions: Object): Function {
        extendOptions = extendOptions || {}
        const Super = this
        const Sub = function VueComponent (options) {
          // 子组件初始化
          this._init(options)
        }
        // 继承Vue
        Sub.prototype = Object.create(Super.prototype)
        Sub.prototype.constructor = Sub
        // 将Vue.options与自身extendOptions合并
        Sub.options = mergeOptions(
          Super.options,
          extendOptions
        )
        return Sub
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // init.js
    export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
      // ...
      const opts = vm.$options = Object.create(vm.constructor.options);
      // ...
    }
    
    1
    2
    3
    4
    5
    6

mixin的一个典型场景,就是在vue-router源码中,通过mixin的方式混入一个全局的beforeCreate函数:

let Vue;
VueRouter.install = (_Vue) => {
    Vue = _Vue;
    Vue.mixin({
        beforeCreate() {
             if (this.$options.router) { // 根组件
                this._routerRoot = this; 
                this._router = this.$options.router;
								// router初始化
                // this._router.init(this);
            } else {
                // 子组件可以通过 this._routerRoot._router
                this._routerRoot = this.$parent && this.$parent._routerRoot;、
            }
        }
    });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

当我们调用Vue.use(VueRouter)时,会执行其install方法,该方法会在全局的Vue.options上填加一个beforeCreated对象,其值是一个数组,数组中的每一项就是对应的fn钩子函数。

image-20210929103336501

当我们在子组件中也有beforeCreated钩子时,这2个钩子会合并到一个数组中,且顺序为[父钩子,子钩子]。因此,当我们在根实例的选项上注入了router实例后,

详细的合并策略,可以参看mergeOptions中关于生命周期钩子的合并mergeHook (opens new window)。

image-20210929103435896

# 缺点

mixin优点很明显,就是可以抽取公共逻辑,进行代码复用。也可以通过一些钩子,做一些特定的逻辑。但是其缺点也很明显:

  • 数据来源不明
  • mixin中可能出现命名冲突