优秀的编程知识分享平台

网站首页 > 技术文章 正文

「 原创 」Vue 源码解析 - 合并配置(一)

nanyue 2024-09-06 20:23:01 技术文章 8 ℃

《new Vue() 之后发生了什么?》new Vue 之后发生的第一件事就是对 options 的合并。配置的合并分为两种情况,一种是直接外部调用,也就是我们日常开发中,调用 new Vue({...}) 实例化 Vue 时的场景,另一个是在组件内部调用 new Vue({...}) 的场景。源码在 /src/core/instance/init.js 中。

先看看第一种,也就是外部调用的场景,也是我们在初始化的时候会执行的部分。

Vue.prototype._init = function (options?: Object) {
  
    // ...
  
    // merge options
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    
  
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')
  
  
  }

该段源码,其调用了 mergeOptions 函数,目的在于合并 resolveConstructorOptions(vm.constructor) 返回值与 options 的合并,先看 resolveConstructorOptions 实现的源码:

export function resolveConstructorOptions (Ctor: Class<Component>) {
  let options = Ctor.options
  if (Ctor.super) {
    const superOptions = resolveConstructorOptions(Ctor.super)
    // ...
  }
  return options
}

这是一个向上递归,不断查找父对象中的 options 过程。

先不看是否有父对象,就假设 Ctor.super 为 false,那这个方法返回的 options 就是 Ctor.options。

这个值是什么呢?Ctor 传过来的是 constructor,其实也就是 Vue,也就是说这个函数最后返回的 options 就是 Vue.options

Vue.options 又是在哪儿定义的呢?在 /src/core/index.js 中,初始化了全局API,也就是调用了 initGlobalAPI() 这个方法,文件在 /src/core/global-api/index.js 中,看看源码就知道,其中有一段代码就是对 options 的定义:

export function initGlobalAPI (Vue: GlobalAPI) {
  // ...
  
  Vue.options = Object.create(null)
  ASSET_TYPES.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })
  // this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  Vue.options._base = Vue
  
  // ...
}

显式创建了一个空对象,接着就是遍历 ASSET_TYPE,ASSET_TYPE 是在 /src/shared/constants.js 中定义的系统内置值(生命周期中的所有钩子函数也都是在这儿定义的)。

export const ASSET_TYPES = [
  'component',
  'directive',
  'filter'
]

知道了 Vue.options 的来源后,再回头看 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
  }

  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)

  // Apply extends and mixins on the child options,
  // but only if it is a raw options object that isn't
  // the result of another mergeOptions call.
  // Only merged options has the _base property.
  if (!child._base) {
    if (child.extends) {
      parent = mergeOptions(parent, child.extends, vm)
    }
    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
}

几个 normalize 规范化操作先不看,继续往下的代码,尤大大在注释上也说的很清楚了,递归调用 mergeOptions 将 extends 和 mixins 合并到 parent 上,也就是 Vue.options 上。

再往下调用 mergeField,设置 options,继续遍历 child,如果不在 parent 中的就在调用 mergeField 继续添加。

细看 mergeField 中的 strats,这个是在 /src/core/config.js 中定义的,其格式为 { [key: string]: Function }, 默认就是个空对象, mergeField 方法中 strats,存在 child 就返回 child,不存在的情况下,返回的就是 parent。

在 /src/core/util/options.js 中,针对 props / methods / computed / provide / assets / 生命周期的钩子 等定义了各种针对性的 key 值的合并策略。调用 mergeOptions 合并配置后的结果,大致如下:

vm.$options = {
    components: {//...},
    directives: {//...},
    filter: {//...},
    _base: function Vue() {//...},
    el: '#app',
    render: function(h) {//...},
    //...
}


小结

常日开发中,我们也常会封装一些东西,然后对外提供一些参数,供用户自定义配置。Vue 合并配置的作用其实也是如此,其内置一些必要的默认值,在初始化阶段将用户传过来的自定义配置进行合并操作,精髓在于精细化的控制,各种针对性的合并策略。


最近发表
标签列表