vue源码:探究mixin与mergeOptions

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

优点:

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

缺点:
1.mixin中的方法以及逻辑不明确,不直观
2.因为过于灵活容易造成滥用

Mixin的实现方式

组件使用mixin的几种方法:
-Vue.mixin():直接调用组件构造函数上的mixin静态方法。
-可以通过mixins:['文件夹']来使用

Vue.mixin源码:

1
2
3
4
5
6
7
8
9
    // 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有这几个:

1
2
3
4
5
export const ASSET_TYPES = [ // 资源类型
 // 每一个Vue组件都会挂载的成员
 'component',
 'directive',
 'filter'

现在来看mergeOptions 里面的源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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中对应的不同类型的处理方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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的合并策略都是将新的同名参数替代旧的参数

在来看看data的合并

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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中所有的属性,在根据不同的情况进行合并