詳解vue 組件注冊(cè)
一、了解組件注冊(cè)的兩種方式
1.1 全局組件的注冊(cè)方法
//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: '' })
上面我們就通過(guò)Vue.component()注冊(cè)了一個(gè)全局組件hello,接下來(lái)分析源碼實(shí)現(xiàn)的時(shí)候也是基于這個(gè)例子來(lái)進(jìn)行的。
1.2 局部組件的注冊(cè)
<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>
像這樣就注冊(cè)了一個(gè)HelloWorld的局部組件。
二、全局組件注冊(cè)的源碼
1.Vue初始化的時(shí)候,會(huì)調(diào)用initGlobalAPI()
//【代碼塊1】 //代碼所在文件:src/core/global-api/index.js export function initGlobalAPI(Vue: GlobalAPI){ //...省略其他無(wú)關(guān)代碼 initAssetRegisters(Vue) //這個(gè)方法就是用于組件注冊(cè)的方法 }
2.在initAssetRegisters()方法中執(zhí)行組件的定義
//【代碼塊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轉(zhuǎn)換為一個(gè)繼承于Vue的構(gòu)造函數(shù) } //...其他類型的處理 this.options[type+'s'][id] = definition //將這個(gè)構(gòu)造函數(shù)掛載到Vue.options.components上 return definition } }) }
此時(shí),我們可以單步調(diào)試一下我們上面的例子,來(lái)看一下definition一開(kāi)始是什么,以及執(zhí)行掛載后Vue.options變成了什么樣子:
a.definition: 其實(shí)傳入的時(shí)候就是我們一開(kāi)始定義的全局組件的具體內(nèi)容
b.Vue.options: 可以看到我們定義的全局組件hello已經(jīng)存在在Vue.options.components上了
3.實(shí)例化組件的時(shí)候,代碼會(huì)執(zhí)行到Vue.prototype._init()上面
//【代碼塊3】 //代碼所在文件:src/core/instance/init.js Vue.prototype._init = function(options){ //..省略其他無(wú)關(guān)代碼 if(options && options._isComponent){ //組件 initInternalComponent(vm, options) }else{ //非組件 vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options||{}, vm ) } }
這里將自己定義的組件的options與Vue.options做了一個(gè)合并,并且賦值給了vm.$options,而通過(guò)【代碼塊2】我們可以知道全局組件的構(gòu)造函數(shù)已經(jīng)被放在了Vue.options.components上,所以經(jīng)過(guò)這一步,vm.$options.components上面也有了全局組件的構(gòu)造函數(shù)。所以現(xiàn)在在任意組件都能拿到全局組件,因?yàn)槿魏谓M件初始化的時(shí)候都會(huì)執(zhí)行這個(gè)合并。
我們可以通過(guò)單步調(diào)試上面的例子看一下現(xiàn)在的vm.$options上面有些什么
4.在創(chuàng)建vnode的過(guò)程中,會(huì)執(zhí)行_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標(biāo)簽 }else if(isDef(Ctor = resolveAsset(context.$options, 'component', tag))){ //已經(jīng)注冊(cè)過(guò)的全局組件 vnode = createComponent(Ctor, data, context, children, tag) }else{ //不是內(nèi)置標(biāo)簽也不是已經(jīng)注冊(cè)過(guò)的組件,就創(chuàng)建一個(gè)全新的vnode vnode = new VNode( tag, data, children, undefined, undefined, context ) } } }
上面代碼中有一個(gè)比較重要的方法resolveAsset(),用于判斷在context.$options.compononts(即vm.$options.components)上面是否能找到這個(gè)組件的構(gòu)造函數(shù),如果能找到,返回這個(gè)構(gòu)造函數(shù),(具體方法見(jiàn)【代碼塊5】)根據(jù)【代碼塊3】我們可以知道如果這個(gè)組件是全局注冊(cè)的組件,那么我們就可以得到這個(gè)構(gòu)造函數(shù),并進(jìn)入這個(gè)else if判斷,通過(guò)createComponent()得到vnode。
5.上面四步已經(jīng)實(shí)現(xiàn)了整個(gè)流程,現(xiàn)在補(bǔ)充看一下resolveAsset()
//【代碼塊5】 //代碼所在文件:src/core/utils/options.js export function resolveAsset(options, type, id, warnMissing){ //options即上面調(diào)用的時(shí)候傳入的context.$options, //由【代碼塊3】,vm.$options是由我們自定義的options以及Vue上的options合并而來(lái)的 //type現(xiàn)在是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 }
先通過(guò) const assets = options[type] 拿到 assets,然后再嘗試拿 assets[id],這里有個(gè)順序,先直接使用 id 拿,如果不存在,則把 id 變成駝峰的形式再拿,如果仍然不存在則在駝峰的基礎(chǔ)上把首字母再變成大寫(xiě)的形式再拿,如果仍然拿不到則報(bào)錯(cuò)。這樣說(shuō)明了我們?cè)谑褂?Vue.component(id, definition) 全局注冊(cè)組件的時(shí)候,id 可以是連字符、駝峰或首字母大寫(xiě)的形式。
三、局部組件的注冊(cè)
1.extend()
組件在執(zhí)行render()的時(shí)候,會(huì)執(zhí)行createComponent函數(shù),在這個(gè)函數(shù)里面會(huì)執(zhí)行extend()函數(shù)生成一個(gè)構(gòu)造函數(shù),也是在這個(gè)extend()函數(shù)中,執(zhí)行了一個(gè)options的合并
//【代碼塊5】 //代碼所在文件:src/core/global-api/extend.js Vue.entend = function(extendOptions){ //... Sub.options = mergeOptions( Super.options, //Vue的options extendOptions //定義組件的那個(gè)對(duì)象 ) //... }
可以看出這里是將自己傳入的options(即定義組件的那個(gè)對(duì)象)與Vue.options合并,然后放到Sub.options上,同時(shí),因?yàn)镾ub.options上面合并了Vue的options,所以組件里面也可以拿到全局注冊(cè)的組件。
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), options||{}, vm ) } }
組件初始化的過(guò)程中會(huì)進(jìn)入if判斷語(yǔ)句,執(zhí)行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中,我們已經(jīng)將局部組件放在了Sub.options上 //所以這里將局部組件的構(gòu)造函數(shù)放在了vm.$options上 //這樣在執(zhí)行【代碼塊4】的時(shí)候同樣也能通過(guò)resolveAsset得到局部注冊(cè)組件的構(gòu)造函數(shù) 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 } }
四、總結(jié)
由于全局注冊(cè)的組件是將組件的構(gòu)造函數(shù)擴(kuò)展到了Vue.options.components上,而組件在初始化的時(shí)候都會(huì)將自身options與Vue.options合并,擴(kuò)展到當(dāng)前組件的vm.$options.components下,所以全局組件能在任意組件被使用。而局部注冊(cè)的組件是將組件的構(gòu)造函數(shù)擴(kuò)展到了當(dāng)前組件的vm.$options.components下,所以只能在當(dāng)前組件使用。
以上就是詳解vue 組件注冊(cè)的詳細(xì)內(nèi)容,更多關(guān)于vue 組件注冊(cè)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
理解Proxy及使用Proxy實(shí)現(xiàn)vue數(shù)據(jù)雙向綁定操作
這篇文章主要介紹了理解Proxy及使用Proxy實(shí)現(xiàn)vue數(shù)據(jù)雙向綁定操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-07-07Vue3 composition API實(shí)現(xiàn)邏輯復(fù)用的方法
本文主要介紹了Vue3 composition API實(shí)現(xiàn)邏輯復(fù)用的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08Vue項(xiàng)目中使用flow做類型檢測(cè)的方法
這篇文章主要介紹了Vue項(xiàng)目中使用flow做類型檢測(cè)的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03ElementUI 詳細(xì)分析DatePicker 日期選擇器實(shí)戰(zhàn)
這篇文章主要介紹了ElementUI詳細(xì)分析DatePicker 日期選擇器實(shí)戰(zhàn)教程,本文通過(guò)實(shí)例代碼圖文介紹給大家講解的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2024-08-08vue Nprogress進(jìn)度條功能實(shí)現(xiàn)常見(jiàn)問(wèn)題
這篇文章主要介紹了vue Nprogress進(jìn)度條功能實(shí)現(xiàn),NProgress是頁(yè)面跳轉(zhuǎn)是出現(xiàn)在瀏覽器頂部的進(jìn)度條,本文通過(guò)實(shí)例代碼給大家講解,需要的朋友可以參考下2021-07-07淺談vue項(xiàng)目可以從哪些方面進(jìn)行優(yōu)化
本篇文章主要介紹了淺談vue項(xiàng)目可以從哪些方面進(jìn)行優(yōu)化,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-05-05