詳解vue 元件註冊
一、瞭解元件註冊的兩種方式
1.1 全域性元件的註冊方法
//main.js import Vue from 'vue' import App from './App' import router from './router' Vue.config.productionTip = false let Hello = { name: 'hello',template: '這是全域性元件hello' } Vue.component('hello',Hello) new Vue({ el: '#app',router,components: { App },template: '' })
上面我們就通過Vue.component()註冊了一個全域性元件hello,接下來分析原始碼實現的時候也是基於這個例子來進行的。
1.2 區域性元件的註冊
<template> <div id="app"> <img src="./assets/logo.png"> <HelloWorld/> </div> </template> <script> import HelloWorld from './components/HelloWorld.vue' export default { name: 'App',components:{ HelloWorld } } </script>
像這樣就註冊了一個HelloWorld的區域性元件。
二、全域性元件註冊的原始碼
1.Vue初始化的時候,會呼叫initGlobalAPI()
//【程式碼塊1】 //程式碼所在檔案:src/core/global-api/index.js export function initGlobalAPI(Vue: GlobalAPI){ //...省略其他無關程式碼 initAssetRegisters(Vue) //這個方法就是用於元件註冊的方法 }
2.在initAssetRegisters()方法中執行元件的定義
//【程式碼塊2】 //程式碼所在檔案:src/core/global-api/assets.js export function initAssetRegister(Vue){ ASSET_TYPES.forEach(type=>{ //ASSET_TYPES包括component、directive、filter Vue[type] = function(id,definition){ //...一些條件判斷 if(type === 'component' && isPlainObject(definition)){ definition.name = definition.name || id definition = this.options._base.extend(definition) //將definition轉換為一個繼承於Vue的建構函式 } //...其他型別的處理 this.options[type+'s'][id] = definition //將這個建構函式掛載到Vue.options.components上 return definition } }) }
此時,我們可以單步除錯一下我們上面的例子,來看一下definition一開始是什麼,以及執行掛載後Vue.options變成了什麼樣子:
a.definition: 其實傳入的時候就是我們一開始定義的全域性元件的具體內容
b.Vue.options: 可以看到我們定義的全域性元件hello已經存在在Vue.options.components上了
3.例項化元件的時候,程式碼會執行到Vue.prototype._init()上面
//【程式碼塊3】 //程式碼所在檔案:src/core/instance/init.js Vue.prototype._init = function(options){ //..省略其他無關程式碼 if(options && options._isComponent){ //元件 initInternalComponent(vm,options) }else{ //非元件 vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor),options||{},vm ) } }
這裡將自己定義的元件的options與Vue.options做了一個合併,並且賦值給了vm.$options,而通過【程式碼塊2】我們可以知道全域性元件的建構函式已經被放在了Vue.options.components上,所以經過這一步,vm.$options.components上面也有了全域性元件的建構函式。所以現在在任意元件都能拿到全域性元件,因為任何元件初始化的時候都會執行這個合併。
我們可以通過單步除錯上面的例子看一下現在的vm.$options上面有些什麼
4.在建立vnode的過程中,會執行_createElement方法
//【程式碼塊4】 //程式碼所在檔案:src/core/vdom/create-element.js export function _createElement(context,tag,data,children,normalization){ if(typeof tag === 'string'){ //... if(config.isReservedTag(tag)){ //...保留的html標籤 }else if(isDef(Ctor = resolveAsset(context.$options,'component',tag))){ //已經註冊過的全域性元件 vnode = createComponent(Ctor,context,tag) }else{ //不是內建標籤也不是已經註冊過的元件,就建立一個全新的vnode vnode = new VNode( tag,undefined,context ) } } }
上面程式碼中有一個比較重要的方法resolveAsset(),用於判斷在context.$options.compononts(即vm.$options.components)上面是否能找到這個元件的建構函式,如果能找到,返回這個建構函式,(具體方法見【程式碼塊5】)根據【程式碼塊3】我們可以知道如果這個元件是全域性註冊的元件,那麼我們就可以得到這個建構函式,並進入這個else if判斷,通過createComponent()得到vnode。
5.上面四步已經實現了整個流程,現在補充看一下resolveAsset()
//【程式碼塊5】 //程式碼所在檔案:src/core/utils/options.js export function resolveAsset(options,type,id,warnMissing){ //options即上面呼叫的時候傳入的context.$options,//由【程式碼塊3】,vm.$options是由我們自定義的options以及Vue上的options合併而來的 //type現在是components const assets = options[type] // check local registration variations first if (hasOwn(assets,id)) return assets[id] const camelizedId = camelize(id) if (hasOwn(assets,camelizedId)) return assets[camelizedId] const PascalCaseId = capitalize(camelizedId) 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 }
先通過 const assets = options[type] 拿到 assets,然後再嘗試拿 assets[id],這裡有個順序,先直接使用 id 拿,如果不存在,則把 id 變成駝峰的形式再拿,如果仍然不存在則在駝峰的基礎上把首字母再變成大寫的形式再拿,如果仍然拿不到則報錯。這樣說明了我們在使用 Vue.component(id,definition) 全域性註冊元件的時候,id 可以是連字元、駝峰或首字母大寫的形式。
三、區域性元件的註冊
1.extend()
元件在執行render()的時候,會執行createComponent函式,在這個函式裡面會執行extend()函式生成一個建構函式,也是在這個extend()函式中,執行了一個options的合併
//【程式碼塊5】 //程式碼所在檔案:src/core/global-api/extend.js Vue.entend = function(extendOptions){ //... Sub.options = mergeOptions( Super.options,//Vue的options extendOptions //定義元件的那個物件 ) //... }
可以看出這裡是將自己傳入的options(即定義元件的那個物件)與Vue.options合併,然後放到Sub.options上,同時,因為Sub.options上面合併了Vue的options,所以元件裡面也可以拿到全域性註冊的元件。
2.元件初始化
//【程式碼塊6(同程式碼塊3)】 //程式碼所在檔案:src/core/instance/init.js Vue.prototype._init = function(options){ //.. if(options && options._isComponent){ initInternalComponent(vm,options) }else{ vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor),vm ) } }
元件初始化的過程中會進入if判斷語句,執行initInternalComponent()
3.initInternalComponent()
//【程式碼塊7】 //程式碼所在檔案:src/core/instance/init.js export function initInternalComponent (vm: Component,options: InternalComponentOptions) { const opts = vm.$options = Object.create(vm.constructor.options) //vm.constructor即為Sub,在程式碼塊5中,我們已經將區域性元件放在了Sub.options上 //所以這裡將區域性元件的建構函式放在了vm.$options上 //這樣在執行【程式碼塊4】的時候同樣也能通過resolveAsset得到區域性註冊元件的建構函式 const parentVnode = options._parentVnode opts.parent = options.parent opts._parentVnode = parentVnode //將componentOptions裡面的別的屬性賦值給opts const vnodeComponentOptions = parentVnode.componentOptions 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 } }
四、總結
由於全域性註冊的元件是將元件的建構函式擴充套件到了Vue.options.components上,而元件在初始化的時候都會將自身options與Vue.options合併,擴充套件到當前元件的vm.$options.components下,所以全域性元件能在任意元件被使用。而區域性註冊的元件是將元件的建構函式擴充套件到了當前元件的vm.$options.components下,所以只能在當前元件使用。
以上就是詳解vue 元件註冊的詳細內容,更多關於vue 元件註冊的資料請關注我們其它相關文章!