Vue2.0原始碼學習(6) - 元件註冊

Inès發表於2022-02-22

元件註冊

前言

在 Vue.js 中,除了它內建的元件如 keep-alive、component、transition、transition-group 等,其它使用者自定義元件在使用前必須註冊。在開發過程中可能會遇到如下報錯資訊:

Unknown custom element: <app> - did you register the component correctly? 
For recursive components, make sure to provide the "name" option.

一般報這個錯的原因都是我們使用了未註冊的元件。Vue.js 提供了 2 種元件的註冊方式,全域性註冊和區域性註冊。接下來我們從原始碼分析的角度來分析這兩種註冊方式。

全域性註冊

在初始化載入階段會呼叫initAssetRegisters函式把需要註冊的元件掛載到Vue.options上。

// src\core\global-api\index.js
initAssetRegisters(Vue)
// src\core\global-api\assets.js
export function initAssetRegisters (Vue: GlobalAPI) {
  // 標註①
  ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
      if (!definition) {
        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)) {
          // 優先拿name,沒有則取id
          definition.name = definition.name || id
          // 標註②
          definition = this.options._base.extend(definition)
        }
        if (type === 'directive' && typeof definition === 'function') {
          definition = { bind: definition, update: definition }
        }
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })
}

標註①:
對ASSET_TYPES進行遍歷,我們先看看遍歷物件ASSET_TYPES是什麼?

// src\shared\constants.js
export const ASSET_TYPES = [
  'component',
  'directive',
  'filter'
]

其實就是存放著外掛、指令、過濾器這三個分類名稱的陣列,這裡我們只單獨針對component進行分析。
標註②:
this.options._base其實是Vue,具體原因請檢視之前的文章《元件的建立和patch過程》
通過Vue.extend把物件轉換成構造器。
最後把definition放到this.options即Vue.options上,然後return definition。
雖然掛載到Vue.options上,但是又是什麼時候會被拿去註冊成真正的元件呢?
我們回顧_createElement函式:

// src\core\vdom\create-element.js
export function _createElement (
  context: Component,
  tag?: string | Class<Component> | Function | Object,
  data?: VNodeData,
  children?: any,
  normalizationType?: number
): VNode | Array<VNode> {
    ...
    if (typeof tag === 'string') {
        //是否HTML原生標籤
        if (config.isReservedTag(tag)) {
            ...
        // 標註①:resolveAsset
        } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
            // component
            vnode = createComponent(Ctor, data, context, children, tag)
        } else {
            ...
        }
    }
}

標註①:resolveAsset函式做了什麼?

// src\core\util\options.js
export function resolveAsset (
  options: Object,
  type: string,
  id: string,
  warnMissing?: boolean
): any {
  /* istanbul ignore if */
  if (typeof id !== 'string') {
    return
  }
  const assets = options[type]
  //判斷配置中是否存在該元件
  if (hasOwn(assets, id)) return assets[id]
  const camelizedId = camelize(id)
  //id轉換成駝峰型判斷
  if (hasOwn(assets, camelizedId)) return assets[camelizedId]
  const PascalCaseId = capitalize(camelizedId)
  //id轉換成首字母大寫判斷
  if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId]
  // fallback to prototype chain
//   原型上面找
  const res = assets[id] || assets[camelizedId] || assets[PascalCaseId]
  if (process.env.NODE_ENV !== 'production' && warnMissing && !res) {
    warn(
      'Failed to resolve ' + type.slice(0, -1) + ': ' + id,
      options
    )
  }
  //返回構造器
  return res
}

其實就是經過各種情況判斷識別Vue.options是否有定義該元件,有的話則返回,然後最後經過createComponent函式進行了元件的註冊。

區域性註冊

區域性註冊其實和全域性註冊的幾乎一樣,只是它需要在此前做一個option合併:

// src\core\global-api\extend.js
Sub.options = mergeOptions(
  Super.options,
  extendOptions
)

關於合併的詳細分析請查閱之前文章《合併配置》
由於合併配置是掛載於Sub上的,也就是說它只是一個在當前Sub作用域下的,一次這種建立方式的元件只能區域性使用。

相關文章