美文网首页
vue源码:探究mixin与mergeOptions

vue源码:探究mixin与mergeOptions

作者: 年轻人不喝鸡汤 | 来源:发表于2020-10-14 11:04 被阅读0次

vue中的mixin可以实现一些逻辑复用,我们来看看mixin的好处和一些不足。

优点:

  1. 可以提取一些功能(组件)的属性混合到另一个组件中或者全局对象中,灵活度高,耦合度低,便于维

缺点:
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对应属性合并成数组类型。

相关文章

网友评论

      本文标题:vue源码:探究mixin与mergeOptions

      本文链接:https://www.haomeiwen.com/subject/tjvipktx.html