源码分析,vue.use()的时候发生了什么,以初始化vuex为例

了解Vue.use方法原理对开发vue插件是很有帮助的,Vue.use方法是在初始化Vue全局API(initGlobalAPI)的时候挂到Vue上面的,代码如下:

/* @flow */

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

export function initUse (Vue: GlobalAPI) {
    
    
  Vue.use = function (plugin: Function | Object) {
    
    
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    if (installedPlugins.indexOf(plugin) > -1) {
    
    
      return this
    }

    // additional parameters
    const args = toArray(arguments, 1)
    args.unshift(this)
    if (typeof plugin.install === 'function') {
    
    
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
    
    
      plugin.apply(null, args)
    }
    installedPlugins.push(plugin)
    return this
  }
}

这个方法可以分为两步:
1.首先将Vue的_installedPlugins属性赋值给_installedPlugins变量,没有该属性的话则初始化一个空数组。有的话,则判断插件有没有被安装过,安装过则直接返回,避免重复安装。
2.args是存储额外传递的参数,也就是Vue.use(‘name’, args)的时候可以传递参数;第一个参数是this,也就是class Vue。接下来,判断插件有无install方法,有的话直接执行插件的install方法,在vuex的初始化过程中是有install方法的,代码如下:

Vue.use(Store)
export function install (_Vue) {
    
    
  if (Vue && _Vue === Vue) {
    
    
    if (process.env.NODE_ENV !== 'production') {
    
    
      console.error(
        '[vuex] already installed. Vue.use(Vuex) should be called only once.'
      )
    }
    return
  }
  Vue = _Vue
  applyMixin(Vue)
}

这段代码也判断是否已经安装过了vuex,安装过了则报出警告,否则将传入的_Vue赋值给变量Vue(第一次install的时候Vue为undefined),接下来执行applyMixin(),代码如下:

export default function (Vue) {
    
    
  const version = Number(Vue.version.split('.')[0])

  if (version >= 2) {
    
    
    Vue.mixin({
    
     beforeCreate: vuexInit })
  } else {
    
    
    // override init and inject vuex init procedure
    // for 1.x backwards compatibility.
    const _init = Vue.prototype._init
    Vue.prototype._init = function (options = {
     
     }) {
    
    
      options.init = options.init
        ? [vuexInit].concat(options.init)
        : vuexInit
      _init.call(this, options)
    }
  }

  /**
   * Vuex init hook, injected into each instances init hooks list.
   */

  function vuexInit () {
    
    
    const options = this.$options
    // store injection
    if (options.store) {
    
    
      this.$store = typeof options.store === 'function'
        ? options.store()
        : options.store
    } else if (options.parent && options.parent.$store) {
    
    
      this.$store = options.parent.$store
    }
  }
}

这段代码首先判断了vuex的版本,这里只分析2以上的版本,使用了Vue.mixin()在钩子函数beforeCreate中添加了vuexInit方法,在beforeCreate执行的时候触发,vuexInit保证了在所有的组件中都能通过this.$store来访问Store对象。那beforeCreate是如何加入Vue中的呢,接着看Vue.mixin的源码(其实Vue的mixin语法原理也是使用的Vue.mixin方法):

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

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
  }

  // 这儿对一些属性进行规范化
  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)
  const extendsFrom = child.extends
   // 孩子如果有extends属性,则递归调用mergeOptions
  if (extendsFrom) {
    
    
    parent = mergeOptions(parent, extendsFrom, vm)
  }
  // 孩子如果有mixins属性,则递归调用mergeOptions
  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)
  }
  for (key in child) {
    
    
    if (!hasOwn(parent, key)) {
    
    
      mergeField(key)
    }
  }
  function mergeField (key) {
    
    
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

Vue.mixin中主要使用的是mergeOptions(合并配置)方法,mergeOptions中进行了一些属性的规范化(这里不多赘述),在vuex的初始化过程中主要关注:

for (key in parent) {
    
    
    mergeField(key)
  }

这个时候parent其实Vue上已经有了一个钩子函数beforeCreate(在Vue 实例化的过程中产生的),执行mergeField

/**
 * Option overwriting strategies are functions that handle
 * how to merge a parent option value and a child option
 * value into the final value.
 */
var strats = config.optionMergeStrategies;

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

function mergeHook (
  parentVal,
  childVal
) {
    
    
  var res = childVal
    ? parentVal
      ? parentVal.concat(childVal)
      : Array.isArray(childVal)
        ? childVal
        : [childVal]
    : parentVal;
  return res
    ? dedupeHooks(res)
    : res
}

针对不同的属性,Vue有不同的合并策略,在vuex install的过程中只关注对于钩子函数的合并策略,因为传入的是Vue.mixin({ beforeCreate: vuexInit })。合并策略如上,在子元素有值且父元素有值的情况下,concat两个钩子函数,最终的beforeCreate其实是一个数组。在触发beforeCreate的过程中其实就是遍历了beforeCreate这个钩子数组,依次执行了数组中各个函数,(对于父子组件的钩子函数触发时机原理其实与此类似)。

猜你喜欢

转载自blog.csdn.net/m0_52544128/article/details/121774110