vue混入mixin浅析

7 篇文章 0 订阅

mixin

基础

混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

局部混入
// 定义一个混入对象
var myMixin = {
  created: function () {
    this.hello()
  },
  methods: {
    hello: function () {
      console.log('hello from mixin!')
    }
  }
}

// 定义一个使用混入对象的组件
var Component = Vue.extend({
  mixins: [myMixin]
})

var component = new Component() // => "hello from mixin!"
全局混入

混入也可以进行全局注册。使用时格外小心!一旦使用全局混入,它将影响每一个之后创建的 Vue 实例。

// 为自定义的选项 'myOption' 注入一个处理器。
Vue.mixin({
  created: function () {
    var myOption = this.$options.myOption
    if (myOption) {
      console.log(myOption)
    }
  }
})

new Vue({
  myOption: 'hello!'
})
// => "hello!"

使用场景

多个组件中使用了同一个变量、方法,为了避免在每个组件中都写一份,提高代码的重用性,可以采用mixin混如到各个组件中使用,方便统一管理。

特点

混入对象可以包含任意组件选项,可以定义Data、methods、Components、LifeCycle Hooks(生命周期函数)、Directives(指令)、路由钩子函数等。

方法和参数在各组件中不共享

如混入对象中有一个 num:1的变量,在组件A中改变num值为5,这时候在组件B中获取这个值,拿到的还是1,还是混入对象里的初始值,数据不共享。

与vuex的区别
  • vuex:用来做状态管理的,里面定义的变量在每个组件中均可以使用和修改,在任一组件中修改此变量的值之后,其他组件中此变量的值也会随之修改。
  • mixins:可以定义共用的变量,在每个组件中使用,引入组件中之后,各个变量是相互独立的,值的修改在组件中不会相互影响。
与公共组件的区别

在父组件中引入组件,相当于在父组件中给出一片独立的空间供子组件使用,然后根据props来传值,但本质上两者是相对独立的。

与extens的区别

extends只能继承一个,mixin可以是多个。

合并顺序

在这里插入图片描述

  • 全局注册的混入最先完成混入,并按注册的顺序来逐个合并,先注册的先完成混入合并,依次类推
  • 局部注册的混入次之,并按mixins数组里声明的顺序依次完成合并
  • 每个混入也可以包含mixins局部混入数组,mixins先完成合并,本混入的options再进行合并
  • 组件options最后完成混入合并
  • 先合并的”优先级”低,后合并的”优先级”高,也就是组件的options合并优先级最高
  • 不同的选项也会根据自身的混入策略进行合并

mixin合并策略

核心代码

源码位置:src/core/util/options.js

export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  // ...省略

  // 判断有没有mixin 也就是mixin里面挂mixin的情况 有的话递归进行合并
  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
  for (key in parent) {
    mergeField(key) // 先遍历parent的key
  }
  for (key in child) {
    // 如果parent已经处理过某个key 就不处理了
    if (!hasOwn(parent, key)) {
      mergeField(key) // 处理child中的key 也就parent中没有处理过的key
    }
  }

  function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key) // 根据不同类型的options调用strats中不同的方法进行合并
  }
  return options
}

const defaultStrat = function (parentVal: any, childVal: any): any {
  return childVal === undefined
    ? parentVal
    : childVal
}
  • 优先递归处理 mixins

  • 先遍历合并 parent 中的key,调用mergeField方法进行合并,然后保存在变量options

  • 再遍历 child,合并补上 parent 中没有的key,调用mergeField方法进行合并,保存在变量options

  • strats的值如下:
    在这里插入图片描述

替换型

props、methods、inject、computed属于替换型。

// 源码位置:src/core/util/options.js
strats.props =
strats.methods =
strats.inject =
strats.computed = function (
  parentVal: ?Object,
  childVal: ?Object,
  vm?: Component,
  key: string
): ?Object {
	// ...省略
  if (!parentVal) return childVal // 如果parentVal没有值,直接返回childVal
  const ret = Object.create(null)
  extend(ret, parentVal) // extend方法实际是把parentVal的属性复制到ret中
  if (childVal) extend(ret, childVal) // 把childVal的属性复制到ret中
  return ret
}

// 源码位置:src/shared/util.js
export function extend (to: Object, _from: ?Object): Object {
  for (const key in _from) {
    to[key] = _from[key]
  }
  return to
}
合并型

data属于合并型,我们将源码简写一下。

// 源码位置:src/core/util/options.js
strats.data = function (
  parentVal: any,
  childVal: any,
  vm?: Component
): ?Function {
	// ...省略
  return mergeDataOrFn(parentVal, childVal, vm)
}

export function mergeDataOrFn (
  parentVal: any,
  childVal: any,
  vm?: Component
): ?Function {
  // ...省略
  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
      }
    }
}

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(to, key, fromVal)
    } 
    // 存在相同属性,合并对象
    else if (
      toVal !== fromVal &&
      isPlainObject(toVal) &&
      isPlainObject(fromVal)
    ) {
      mergeData(toVal, fromVal)
    }
  }
  return to
}
  • 当目标 data 对象不包含当前属性时,调用 set 方法进行合并。
  • 当目标 data 对象包含当前属性并且当前值为纯对象时,递归合并当前对象值,这样做是为了防止对象存在新增属性。
  • 若child无此key,parent有,直接合并此key。
  • 若child和parent都有此key,且非object类型,忽略不作为。
  • 若child和parent都有此key,且为object类型,则递归合并对象。
队列型

生命周期函数钩子属于队列型,先进先出。

// 源码位置:src/shared/constants.js
export const LIFECYCLE_HOOKS = [
  'beforeCreate',
  'created',
  'beforeMount',
  'mounted',
  'beforeUpdate',
  'updated',
  'beforeDestroy',
  'destroyed',
  'activated',
  'deactivated',
  'errorCaptured',
  'serverPrefetch'
]

// 源码位置:src/core/util/options.js
import { LIFECYCLE_HOOKS } from 'shared/constants'

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

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
}

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
}

Vue 实例的生命周期钩子被合并为一个数组,然后正序遍历一次执行。

总结

  • 全局注册的混入最先完成混入,并按注册的顺序来逐个合并,先注册的先完成混入合并,依次类推
  • 局部注册的混入次之,并按mixins数组里声明的顺序依次完成合并
  • 每个混入也可以包含mixins局部混入数组,mixins先完成合并,本混入的options再进行合并
  • 组件options最后完成混入合并
  • 不同的类型走不同的合并策略
    • 替换型策略:props、methods、inject、computed, 就是将新的同名参数替代旧的参数。
    • 合并型策略:data,通过set方法进行合并和重新赋值。
    • 队列型策略:生命周期函数,原理是将函数存入一个数组,然后正序遍历依次执行。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值