元件註冊
前言
在 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作用域下的,一次這種建立方式的元件只能區域性使用。