源码分析:Vue混入语法,Vue.mixin原理

Mixin

Vue提供了一个混入语法 mixin,使复用代码变得更加灵活,
在Vue中可以使用两种方法来混入对象,包括组件内部混入:

const myMixin = {
    created() {
        console.log('local mixin')
    }
}

// 组件内部
export default {
    name: '',
    mixins: [myMixin],
    created() {
        console.log('component')
    }
}

以及全局混入:

Vue.mixin({
    created() {
        console.log('vue mixin')
    }
})

现在这个地方就有两个个问题了。

1.全局混入后为什么在所有的组件中都能访问到混入的这个属性?

2.当组件中也存在与混入对象上相同的属性时,该怎么处理呢?

带着这两个问题,我们去看源码吧!

全局混入 Vue.mixin

Vue.mixin的源码在src/core/global-api/mixin.js中:

import { mergeOptions } from '../util/index'

export function initMixin (Vue: GlobalAPI) {
  Vue.mixin = function (mixin: Object) {
    // this 指向Vue
    this.options = mergeOptions(this.options, mixin)
    return this
  }
}

其实这段代码也就是给Vue上的options属性与混入的对象属性进行了mergeOptions操作,并把返回的值重新赋值给了Vue.options。继续看mergeOptions的逻辑,定义在src/core/util/options.js中:

/**
 * Option overwriting strategies are functions that handle
 * how to merge a parent option value and a child option
 * value into the final value.
 */
 // 起始时这是一个空对象
const strats = config.optionMergeStrategies

/**
 * Merge two option objects into a new one.
 * 将两个选项配置合并成一个新的
 * Core utility used in both instantiation and inheritance.
 */
export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  if (process.env.NODE_ENV !== 'production') {
    // 如果合并的对象中如果有components属性,需要检查属性上key的命名是否规范
    checkComponents(child)
  }

  //  第二个参数如果是函数,则取函数上面的options
  if (typeof child === 'function') {
    child = child.options
  }

  // 规范化props属性
  normalizeProps(child, vm)
  // 规范化inject属性
  normalizeInject(child, vm)
  // 规范化directives属性
  normalizeDirectives(child)
  // 若child对象上存在extends,将扩展属性也合并到Vue.options上面
  const extendsFrom = child.extends
  if (extendsFrom) {
    parent = mergeOptions(parent, extendsFrom, vm)
  }
  // 若child上面存在mixins属性,依次遍历mixins数组,合并到Vue.options上面
  if (child.mixins) {
    for (let i = 0, l = child.mixins.length; i < l; i++) {
      parent = mergeOptions(parent, child.mixins[i], vm)
    }
  }
  // 初始化空对象
  const options = {}
  let key
  // 遍历Vue.options的key,调用mergeField
  for (key in parent) {
    mergeField(key)
  }
  // 遍历child对象上的key,如果不是Vue.options上的属性,调用mergeField
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  // 针对传入的属性key不同,定义有不同的函数,从而执行不同的合并策略函数
  function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

可以看到这个函数是将传入的对象的一些属性做了一些规范化,并针对传入对象上的不同的key执行不同的strat[key]函数。vue内部针对一些属性定义了strat[key]函数,包括data、生命周期函数、静态方法(conponents、directives、filter)、watch、props、methods、inject、computed等。接下来我们分析下生命周期函数以及部分属性的合并函数:

1.生命周期函数合并

/**
 * Hooks and props are merged as arrays.
 */
function mergeHook (
  parentVal: ?Array<Function>,
  childVal: ?Function | ?Array<Function>
): ?Array<Function> {
  // 分为三种场景,见下文
  return childVal
    ? parentVal
      ? parentVal.concat(childVal)
      : Array.isArray(childVal)
        ? childVal
        : [childVal]
    : parentVal
}

LIFECYCLE_HOOKS.forEach(hook => {
  strats[hook] = mergeHook
})

export const LIFECYCLE_HOOKS = [
  'beforeCreate',
  'created',
  'beforeMount',
  'mounted',
  'beforeUpdate',
  'updated',
  'beforeDestroy',
  'destroyed',
  'activated',
  'deactivated',
  'errorCaptured'
]

生命周期的合并函数都是mergeHook,分为三种场景:

  1. 传入对象没有对应属性,直接返回Vue.options上面的生命周期函数;
  2. 传入对象有属性,Vue.options上面没有属性,将传入对象的函数转换成数组的形式存放;
  3. 传入对象与Vue.options上都存在相同的生命周期函数,则使用concat拼接成新数组,传入对象的生命周期函数放在数组后面。

2.props、methods等属性合并

/**
 * Other object hashes.
 */
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)
  }
  // 以props为例,父没有props属性,返回child
  if (!parentVal) return childVal
  const ret = Object.create(null)
  // 父上面的值复制到空对象ret上面
  extend(ret, parentVal)
  // 以props为例,将child上面的属性赋值到ret上面,有相同值则直接覆盖
  if (childVal) extend(ret, childVal)
  return ret
}

props、methods、inject、computed的合并策略方法是相同的,较为简单。

子对象的props与父对象上的props等有key相同的属性值的时候,子对象上面的值直接覆盖父对象上面的值。

3.默认方法

/**
 * Default strategy.
 */
const defaultStrat = function (parentVal: any, childVal: any): any {
  return childVal === undefined
    ? parentVal
    : childVal
}

对与vue内部未定义合并方法的属性,则执行默认合并方法,子对象有值,直接返回子对象的值,否则返回父对象的值。

4.自定义配置函数

当我们自己在外部定义了一个配置属性,并希望为他定制合并策略方法该怎么做,官方提供了一个配置api

Vue.config.optionMergeStrategies.myOption = function (toVal, fromVal) { 
    // 返回合并后的值 
}

也可以直接将内置的合并方法赋值给自定义合并方法:

var strategies = Vue.config.optionMergeStrategies 
strategies.myOption = strategies.mounted

此外,你也可以改写内置的合并策略方法

Vue.config.optionMergeStrategies.mounted = function(toVal, fromVal) {
    // 返回合并后的值
}

组件内部混入 Vue.mixin

组件构造器在生成的时候,有下面一段逻辑(可参考Vue.extend),也会执行mergeOptions,会将Vue的options与组件内部的options进行合并,而mergeOptions内部有一段逻辑,遍历组件内部的mixins,依次调用mergeOptions,最终子组件的options上面就进行了合并配置的操作了,并将合并后的options结果返回给组件构造器的options上。

const Sub = function VueComponent (options) {
  this._init(options)
}
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
Sub.cid = cid++
Sub.options = mergeOptions(
  Super.options,
  extendOptions
)
// 若child上面存在mixins属性,依次遍历mixins数组,合并到Vue.options上面 
if (child.mixins) { 
    for (let i = 0, l = child.mixins.length; i < l; i++) {
        // parent是父对象的options
        parent = mergeOptions(parent, child.mixins[i], vm) 
    }
}

总结

通过以上的分析:
前文我们提出的两个问题我们就已经清楚了:

1.全局混入后为什么在所有的组件中都能访问到混入的这个属性?

因为全局混入的对象合并在Vue的options中,而所有组件构造器生成的过程中,都会合并Vue.options,所以全局混入后,所有的组件中都能访问到混入的属性。

2.当组件中也存在与混入对象上相同的属性时,该怎么处理呢?
从上文分析中可以看出,不同的属性有不同的合并策略,同时我们在外部
也可以重写vue内部的合并策略,也可以自定义新增属性的合并策略,通过Vue.config.optionMergeStrategies[key]这个属性去配置。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值