使用vue开发过程中,常用的抽离公共逻辑代码的方式有两种,一种是不依赖Vue实例的纯数据处理代码,可以写成utils工具方法;一种是依赖Vue实例的逻辑代码,则使用混入(Mixin)。混入虽然好用,但是随着项目代码增多,其管理和维护的成本极速增大,牵一发而动全身,Vue3.0 Composition API 便是试图解决这类问题,感兴趣的同学可以阅读浅尝vue3.0,万字总结初步了解。
本文使用Vue版本2.6.2进行分析,例子来自于Vue官方教程。
Mixin的使用方法非常简单,它可以混入包含任意组件选项的对象至目标组件中。在未定义“混入”规则的情况下,Vue本身会采取默认的规则将这些选项“合并”至目标组件中。
举个例子:
// 定义一个混入对象 var myMixin = { created () { this.sayHello() }, methods: { sayHello: function () { console.log('hello mixin') } } } // 定义一个使用混入对象的目标组件 var Component = Vue.extend({ mixins: [myMixin] }) var component = new Component() // => "hello mixin"当目标组件和待混入的对象含有同名组件选项时,vue默认的“合并规则”如下:
data对象的内部会进行递归合并,当发生冲突时优先使用目标组件的数据。
从源码中我们可以看到,数据对象会递归的执行合并操作,如果出现相同则不进行合并,即使用目标组件自身的数据
function mergeData (to: Object, from: ?Object): Object { if (!from) return to let key, toVal, fromVal // 获取对象的键名 const keys = hasSymbol ? Reflect.ownKeys(from) : Object.keys(from) for (let i = 0; i < keys.length; i++) { key = keys[i] // 具有可观察对象实例,直接跳过,不需要更换 if (key === '__ob__') continue toVal = to[key] fromVal = from[key] // 属性名相同时不进行赋值操作,即使用目标组件的值 if (!hasOwn(to, key)) { // 新增属性,使用set方法将其变为响应式 set(to, key, fromVal) } else if ( toVal !== fromVal && isPlainObject(toVal) && isPlainObject(fromVal) ) { // 对象递归执行 mergeData(toVal, fromVal) } } return to } export function mergeDataOrFn ( parentVal: any, childVal: any, vm?: Component ): ?Function { if (!vm) { // in a Vue.extend merge, both should be functions if (!childVal) { return parentVal } if (!parentVal) { return childVal } // when parentVal & childVal are both present, // we need to return a function that returns the // merged result of both functions... no need to // check if parentVal is a function here because // it has to be a function to pass previous merges. return function mergedDataFn () { return mergeData( typeof childVal === 'function' ? childVal.call(this, this) : childVal, typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal ) } } else { return function mergedInstanceDataFn () { // instance merge const instanceData = typeof childVal === 'function' ? childVal.call(vm, vm) : childVal const defaultData = typeof parentVal === 'function' ? parentVal.call(vm, vm) : parentVal if (instanceData) { return mergeData(instanceData, defaultData) } else { return defaultData } } } } strats.data = function ( parentVal: any, childVal: any, vm?: Component ): ?Function { if (!vm) { if (childVal && typeof childVal !== 'function') { process.env.NODE_ENV !== 'production' && warn( 'The "data" option should be a function ' + 'that returns a per-instance value in component ' + 'definitions.', vm ) return parentVal } return mergeDataOrFn(parentVal, childVal) } return mergeDataOrFn(parentVal, childVal, vm) }同名的钩子函数会合并成一个数组,混入对象的钩子函数先执行。
值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。