Vue 源码-initMixin

91 阅读1分钟

vue版本:v2.7.10

initMixin

src/core/instance/init.ts

let uid = 0
export function initMixin(Vue: typeof Component) {
  Vue.prototype._init = function (options?: Record<string, any>) {
    const vm: Component = this
    vm._uid = uid++
    ...
    // 合并 options
    if (options && options._isComponent) {
      initInternalComponent(vm, options as any)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor as any),
        options || {},
        vm
      )
    }
    if (__DEV__) {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    // 调用beforeCreate钩子函数
    callHook(vm, 'beforeCreate', undefined, false)
    initInjections(vm)
    initState(vm)
    initProvide(vm) 
    // 调用created钩子函数
    callHook(vm, 'created')
    // 如果options中有el元素,则直接将vm挂载在el上
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}

uid为Vue实例的id值,保存在模块中作为缓存值,每次创建Vue实例递增并赋值给新增实例的_uid。

合并Options

initInternalComponent

单文件组件调用initInternalComponent

export function initInternalComponent(
  vm: Component,
  options: InternalComponentOptions
) {
  const opts = (vm.$options = Object.create((vm.constructor as any).options))
  const parentVnode = options._parentVnode
  //为vm.$options添加parent、parentVnode
  opts.parent = options.parent
  opts._parentVnode = parentVnode

  const vnodeComponentOptions = parentVnode.componentOptions!
  //从options._parentVnode中获取propsData、listeners、children、tag添加到vm.$options中
  opts.propsData = vnodeComponentOptions.propsData
  opts._parentListeners = vnodeComponentOptions.listeners
  opts._renderChildren = vnodeComponentOptions.children
  opts._componentTag = vnodeComponentOptions.tag

  if (options.render) {
    opts.render = options.render
    opts.staticRenderFns = options.staticRenderFns
  }
}

mergeOptions

非单文件组件调用mergeOptions

export function mergeOptions(
  parent: Record<string, any>,
  child: Record<string, any>,
  vm?: Component | null
): ComponentOptions {
  ...
  if (isFunction(child)) {
    child = child.options
  }
  //props、inject、directives数据格式统一化  
  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)

  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: ComponentOptions = {} as any
  let key
  for (key in parent) {
    mergeField(key)
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  function mergeField(key: any) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}
  • 首先对props、inject、directives数据格式统一化
  • option默认合并规则为:组件实例 > 组件mixins > 组件构造函数
  • 可通过Vue.config.optionMergeStrategies调整options合并规则。

initLifecycle

做了以下几件事情

  1. 获取非abstract父组件,赋值给vm.$parent
  2. 通过parent获取到root,赋值给vm.root,赋值给vm.root
  3. 初始化childrenchildren、refs
  4. 若父组件有_provided,赋值给vm._provided,否则赋值为null
  5. 初始化vm几个变量:
  • _watcher: 监听器
  • _inactive: keep-alive组件从DOM中被移除
  • _directInactive: 还没理解
  • _isMounted: 是否已挂载
  • _isDestroyed:是否已销毁
  • _isBeingDestroyed:是否销毁中

export function initLifecycle(vm: Component) {
  const options = vm.$options

  // locate first non-abstract parent
  let parent = options.parent
  if (parent && !options.abstract) {
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    parent.$children.push(vm)
  }

  vm.$parent = parent
  vm.$root = parent ? parent.$root : vm

  vm.$children = []
  vm.$refs = {}

  vm._provided = parent ? parent._provided : Object.create(null)
  vm._watcher = null
  vm._inactive = null
  vm._directInactive = false
  vm._isMounted = false
  vm._isDestroyed = false
  vm._isBeingDestroyed = false
}

initEvents

主要做了两件事:

  • 在所有事件抛出异常之后调用vm实例和全局的异常回调事件
  • 通过vm.onvue实例添加监听事件,通过vm.on为vue实例添加监听事件, 通过vm.off为vue实例移除监听事件

initRender

export function initRender(vm: Component) {
  const options = vm.$options
  // 初始化$vode
  const parentVnode = (vm.$vnode = options._parentVnode!) 
  const renderContext = parentVnode && (parentVnode.context as Component)
  // 解析出插槽和作用域插槽
  vm.$slots = resolveSlots(options._renderChildren, renderContext)
  vm.$scopedSlots = parentVnode
    ? normalizeScopedSlots(
        vm.$parent!,
        parentVnode.data!.scopedSlots,
        vm.$slots
      )
    : emptyObject
  // 添加内部创建元素的函数
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
  // 添加对外开放的创建元素的函数
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
  const parentData = parentVnode && parentVnode.data
  // 设置$attrs和$listeners为响应式数据
  if (__DEV__) {
    defineReactive(
      vm,
      '$attrs',
      (parentData && parentData.attrs) || emptyObject,
      () => {
        !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm)
      },
      true
    )
    defineReactive(
      vm,
      '$listeners',
      options._parentListeners || emptyObject,
      () => {
        !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm)
      },
      true
    )
  } else {
    defineReactive(
      vm,
      '$attrs',
      (parentData && parentData.attrs) || emptyObject,
      null,
      true
    )
    defineReactive(
      vm,
      '$listeners',
      options._parentListeners || emptyObject,
      null,
      true
    )
  }
}

以下在后续深入研究:

  • resolveSlots
  • normalizeScopedSlots
  • createElement
  • defineReactive

initInjections

初始化inject,后续再研究

initState

初始化

  • Props
  • Methods
  • Data
  • Computed
  • Watch 以及Composition-api的实现 这几部分在下一篇展开研究

initProvide

初始化Provide,后续再研究