源码分析 Vue组件注册(全局注册与局部注册)(上)

1,076 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

1.Vue组件注册

我们知道vue中注册组件的方式有两种,一种是全局注册,另一种是局部注册,注册方式如下:

全局注册

const HelloWorld = {
    name: xxx,
    data() {
        return {}
    }
    ...
}
Vue.component('HelloWorld', Helloworld)

局部注册

import HelloWorld from 'xxx/xxx'
export default {
    components: {
        HelloWorld
    }
}

那么为什么全局注册的组件所有地方都能用到?在注册组件过程中又发生了什么呢?接下来我们根据源码来分析!

2.组件全局注册

Vue.component

话不多说,上源码:

Vue.component初始化定义在src/core/global-api/assets目录下:

/* @flow */
import { ASSET_TYPES } from 'shared/constants'
import { isPlainObject, validateComponentName } from '../util/index'

export function initAssetRegisters (Vue: GlobalAPI) {
  /**
   * Create asset registration methods.
   */
  ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
      if (!definition) {
        // 不传入第二个对象,直接返回存在Vue上的对应方法
        return this.options[type + 's'][id]
      } else {
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && type === 'component') {
          // 校验名称
          validateComponentName(id)
        }
        if (type === 'component' && isPlainObject(definition)) {
          // 组件命名
          definition.name = definition.name || id
          // 生成构造器
          definition = this.options._base.extend(definition)
        }
        if (type === 'directive' && typeof definition === 'function') {
          // 如果是detective
          definition = { bind: definition, update: definition }
        }
        // 普通component的情况下,在Vue.options.components[id]中存储构造器
        this.options[type + 's'][id] = definition
        // 若为component,返回构造器
        return definition
      }
    }
  })
}
export const ASSET_TYPES = [
  'component',
  'directive',
  'filter'
]

initAssetRegisters这个函数是在initClobalAPI的函数中执行的,这个是在Vue初始化的过程中执行的。

可以看到这个函数遍历了ASSET_TYPES,在Vue的构造函数上注册了(component、directive、filter)这些方法。

这些注册的方法直接传入一个参数的时候,可以获取已经生成的对应方法或对象。

Vue.component('HelloWorld'),这样执行返回的是一个已经注册过的子类构造器

当我们传入两个参数,也就是通过Vue.component('HelloWorld', Helloworld)调用这个方法的时候,接下来会用validateComponentName校验组件名称:

export function validateComponentName (name: string) {
  if (!/^[a-zA-Z][\w-]*$/.test(name)) {
    warn(
      'Invalid component name: "' + name + '". Component names ' +
      'can only contain alphanumeric characters and the hyphen, ' +
      'and must start with a letter.'
      // 无效的组件名称:“' + name + '”。组件名称只能包含字母数字字符和连字符,而且必须以字母开头。”
    )
  }
  if (isBuiltInTag(name) || config.isReservedTag(name)) {
    warn(
      'Do not use built-in or reserved HTML elements as component ' +
      'id: ' + name
      // 不要使用内置或保留的HTML元素作为组件的id
    )
  }
}

之后主要核心就是使用Vue.extend函数生成了构造器(this.options._base 就是Vue,也是在initGlobalAPI过程中,如下代码),Vue.extend的逻辑可以点击这里

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
  
  ......
  initAssetRegisters(Vue)
}

Vue.extend中有一段逻辑

Sub.options = mergeOptions(
  Super.options,
  extendOptions
)

mergeOptions可以参考mergeOptions

这里也就是将传入的配置对象与Vue的options进行了合并,这样子组件构造器的options里面就有了我们注册的component(这就是为什么这样注册的组件能在全局使用)。

到这里,Vue.component这个函数我们已经清楚是做了什么事情了,也就是在Vue.options.components属性上存储了一个子类构造器,并将这个options与子类的options合并在一起存储在了子类构造器上;那第二个问题来了,这个子类构造器在页面渲染时是如何使用的呢?

我们继续往下分析,点击这里前往下一节

CreateElement