Vue混入Mixin——使用方法、注意事项及源码分析

it2023-08-19  68

使用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选项合并

data对象的内部会进行递归合并,当发生冲突时优先使用目标组件的数据。

例子

var mixin = { data: function () { return { message: 'hello', foo: 'abc' } } } new Vue({ mixins: [mixin], data: function () { return { message: 'goodbye', bar: 'def' } }, created: function () { console.log(this.$data) // => { message: "goodbye", foo: "abc", bar: "def" } } })

源码分析

从源码中我们可以看到,数据对象会递归的执行合并操作,如果出现相同则不进行合并,即使用目标组件自身的数据

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) }

生命周期钩子函数

同名的钩子函数会合并成一个数组,混入对象的钩子函数先执行。

例子

var mixin = { created: function () { console.log('混入对象的钩子被调用') } } new Vue({ mixins: [mixin], created: function () { console.log('组件钩子被调用') } }) // => "混入对象的钩子被调用" // => "组件钩子被调用"

源码分析

目标组件没有这个钩子函数,直接使用混入的钩子函数目标组件有钩子函数: 如果混入对象也该钩子函数,则混入对象的钩子函数拼接目标组件的钩子函数为一个数组如果混入对象没有钩子函数,返回目标组件的狗子函数组成的数组 function mergeHook ( parentVal: ?Array<Function>, childVal: ?Function | ?Array<Function> ): ?Array<Function> { /* 1. 目标组件没有这个钩子函数,直接使用混入的钩子函数 2. 目标组件有钩子函数 2.1 如果混入对象也该钩子函数,则混入对象的钩子函数拼接目标组件的钩子函数为一个数组 2.2 如果混入对象没有钩子函数,返回目标组件的狗子函数组成的数组 */ const res = childVal ? parentVal // 混入对象的钩子函数数组拼接上 ? parentVal.concat(childVal) : Array.isArray(childVal) ? childVal : [childVal] : parentVal return res ? dedupeHooks(res) : res } // 删除重复的钩子函数 function dedupeHooks (hooks) { const res = [] for (let i = 0; i < hooks.length; i++) { // 没有该钩子函数,则加入数组中 if (res.indexOf(hooks[i]) === -1) { res.push(hooks[i]) } } return res } LIFECYCLE_HOOKS.forEach(hook => { strats[hook] = mergeHook })

值为对象的选项

值为对象的选项,例如 methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。

源码分析

strats.props = strats.methods = strats.inject = strats.computed = function ( parentVal: ?Object, childVal: ?Object, vm?: Component, key: string ): ?Object { if (childVal && process.env.NODE_ENV !== 'production') { assertObjectType(key, childVal, vm) } // 混入对象没有,直接使用目标组件的数据 if (!parentVal) return childVal const ret = Object.create(null) // 先混入“混入对象”的数据 extend(ret, parentVal) // 将目标组件的对象混入至对象中,相同属性覆盖。最终使用目标组件数据 if (childVal) extend(ret, childVal) return ret } strats.provide = mergeDataOrFn // shared/utils.js // 将原对象属性混入到目标对象中 export function extend (to: Object, _from: ?Object): Object { for (const key in _from) { to[key] = _from[key] } return to }
最新回复(0)