vue中的mixin可以实现一些逻辑复用,我们来看看mixin的好处和一些不足。
优点:
- 可以提取一些功能(组件)的属性混合到另一个组件中或者全局对象中,灵活度高,耦合度低,便于维
缺点:
1.mixin中的方法以及逻辑不明确,不直观
2.因为过于灵活容易造成滥用
Mixin的实现方式
组件使用mixin的几种方法:
-Vue.mixin():直接调用组件构造函数上的mixin静态方法。
-可以通过mixins:['文件夹']来使用
Vue.mixin源码:
// src/core/global-api/mixin.js
import { mergeOptions } from '../util/index'
export function initMixin (Vue: GlobalAPI) {
Vue.mixin = function (mixin: Object) {
this.options = mergeOptions(this.options, mixin)
return this
}
}
在使用一个Vue.mixin传入一个对象,然后再调用mergeOptions方法传入一个基础的全局options和一个mixin进行合并
全局的基础options有这几个:
export const ASSET_TYPES = [ // 资源类型
// 每一个Vue组件都会挂载的成员
'component',
'directive',
'filter'
现在来看mergeOptions 里面的源代码:
export function mergeOptions (parent: Object, child: Object, vm?: Component): Object {
if (process.env.NODE_ENV !== 'production') {
checkComponents(child)
}
if (typeof child === 'function') {
child = child.options
}
// normalize同字面意思一样,用来规范化属性
normalizeProps(child, vm)
// 规范Inject
normalizeInject(child, vm)
normalizeDirectives(child)
// 未合并的options不带有_base
if (!child._base) {
if (child.extends) {
parent = mergeOptions(parent, child.extends, vm)
}
if (child.mixins) { // 判断有没有mixin,就是mixin里面挂载mixin的情况,有的话就递归合并
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) // 先遍历parent的key,在调用mergeField中的strats[key]进行合并
}
for (key in child) {
if (!hasOwn(parent, key)) { // 判断parent是否已经处理过了
mergeField(key) // 没有处理过,就在进行处理
}
}
function mergeField (key) {
const strat = strats[key] || defaultStrat
options[key] = strat(parent[key], child[key], vm, key) // 根据不同类型的options调用strats不同的方法进行合并
}
return options
}
上面代码的主要作用:
1.优先递归处理mixin
2.遍历合并parent中的key,在调用mergeField 进行合并,保存到变量options
核心在于strats中对应的不同类型的处理方法。
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 //判断parentVal,如果没有就返回parentVal
const ret = Object.create(null) // 创建一个ret对象
extend(ret, parentVal) // extend方法实际是吧parentVal的属性复制到ret中
if (childVal) extend(ret, childVal) // 把childVal的属性复制到ret中
return ret
}
strats.provide = mergeDataOrFn
props、methods、inject、computed的合并策略都是将新的同名参数替代旧的参数
在来看看Vue.data的合并策略:
strats.data = function (parentVal: any, childVal: any, vm?: Component): ?Function {
return mergeDataOrFn(parentVal, childVal, vm)
}
mergeDataOrFn (parentVal: any, childVal: any, vm?: Component): ?Function {
return function mergedInstanceDataFn () {
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) // 将2个对象进行合并
} else {
return defaultData // instanceData没有,就直接返回defaultData
}
}
}
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]
// in case the object is already observed...
if (key === '__ob__') continue
toVal = to[key]
fromVal = from[key]
if (!hasOwn(to, key)) { // 不存在这个属性,就重新设置
set(to, key, fromVal)
} else if ( toVal !== fromVal && isPlainObject(toVal) && isPlainObject(fromVal)) { // 存在相同属性,合并对象
mergeData(toVal, fromVal)
}
}
return to
}
strats.data要遍历data中所有的属性,在根据不同的情况进行合并
1.当目标data对象中不包含当前属性时,调用set方法
2.当目标data对象包含当前属性时,递归合并当前的值,这样做时为了防止对象存在新增属性。
在来看看vue生命周期的合并策略:
function mergeHook (parentVal: ?Array<Function>, childVal: ?Function | ?Array<Function>): ?Array<Function> {
const res = childVal ? parentVal ? parentVal.concat(childVal) : Array.isArray(childVal) ? childVal : [childVal] : parentVal
return res ? dedupeHooks(res) : res
}
LIFECYCLE_HOOKS.forEach(hook => {
strats[hook] = mergeHook // vue实例的生命周期钩子被合并成为一个数组,然后循环执行一遍
})
mergeHook函数会将parentVal和childVal的生命周期是合并成一个数组然后在执行一遍
在来看看watch的合并策略:
// watch挂载的选项不可以直接进行覆盖,需要将每个选项处理成函数数组形式。
strats.watch = function (parentVal: ?Object, childVal: ?Object, vm?: Component, key: string): ?Object {
// Firefox中存在原生的Object.prototype.watch函数
// 为定义watch选项却访问到了watch属性,则重置parentVal与childVal
if (parentVal === nativeWatch) parentVal = undefined
if (childVal === nativeWatch) childVal = undefined
if (!childVal) return Object.create(parentVal || null)
if (process.env.NODE_ENV !== 'production') {
assertObjectType(key, childVal, vm)
}
if (!parentVal) return childVal
const ret = {}
extend(ret, parentVal)
for (const key in childVal) {
let parent = ret[key]
const child = childVal[key]
if (parent && !Array.isArray(parent)) {
parent = [parent]
}
ret[key] = parent ? parent.concat(child) : Array.isArray(child) ? child : [child]
}
return ret
}
Firefox浏览器有原生的Object.prototype.watch函数,在合并watch过程中如果访问到了原生的函数,要兼容处理
合并watch同合并生命周期存在相似之处,都是将选项合并为数组类型。
总结一下
1.我在调用Vue.mixins的时候会通过mergeOptions方法会将全局基础options进行合并
2.在mergeOptions内部优先递归进行mixins的递归合并,然后先父再子调用mergeField进行合并,不同的类型走不同的合并策略
3.合并data:data会首先合并成一个函数,该函数会在组件初始化调用,再执行真正的合并(深度合并)
4.合并声明周期:声明周期会被处理成数组形式,parentVal始终处于数组开始位置。
5.合并props、methods、inject、computed:先合并parentVal,childVal同名属性会进行覆盖。
6.合并watch:合并过程与合并声明周期函数类似,parentVal与childVal对应属性合并成数组类型。
网友评论