1. 程式人生 > 程式設計 >詳解vue 元件註冊

詳解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: 其實傳入的時候就是我們一開始定義的全域性元件的具體內容

詳解vue 元件註冊

b.Vue.options: 可以看到我們定義的全域性元件hello已經存在在Vue.options.components上了

詳解vue 元件註冊

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上面有些什麼

詳解vue 元件註冊

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 元件註冊的資料請關注我們其它相關文章!