Vue源碼--組件註冊

1、瞭解組件註冊的兩種方式

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,接下來分析源碼實現的時候也是基於這個例子來進行的。html

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的局部組件。vue

2、全局組件註冊的源碼

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變成了什麼樣子:node

a.definition: 其實傳入的時候就是咱們一開始定義的全局組件的具體內容

image

b.Vue.options: 能夠看到咱們定義的全局組件hello已經存在在Vue.options.components上了

Vue.options

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
        )
    }
}

這裏將本身定義的組件的optionsVue.options作了一個合併,而且賦值給了vm.$options,而經過【代碼塊2】咱們能夠知道全局組件的構造函數已經被放在了Vue.options.components上,因此通過這一步,vm.$options.components上面也有了全局組件的構造函數。因此如今在任意組件都能拿到全局組件,由於任何組件初始化的時候都會執行這個合併。api

咱們能夠經過單步調試上面的例子看一下如今的vm.$options上面有些什麼app

image

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, data, context, children, tag)
        }else{
            //不是內置標籤也不是已經註冊過的組件,就建立一個全新的vnode
            vnode = new VNode(
                tag, data, children,
                undefined, undefined, context
              )
        }
    }
}

上面代碼中有一個比較重要的方法resolveAsset(),用於判斷在context.$options.compononts(即vm.$options.components)上面是否能找到這個組件的構造函數,若是能找到,返回這個構造函數,(具體方法見【代碼塊5】)根據【代碼塊3】咱們能夠知道若是這個組件是全局註冊的組件,那麼咱們就能夠獲得這個構造函數,並進入這個else if判斷,經過createComponent()獲得vnodedom

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 能夠是連字符、駝峯或首字母大寫的形式。函數

3、局部組件的註冊

1.extend()

組件在執行render()的時候,會執行createComponent函數,在這個函數裏面會執行extend()函數生成一個構造函數,也是在這個extend()函數中,執行了一個options的合併this

//【代碼塊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上面合併了Vueoptions,因此組件裏面也能夠拿到全局註冊的組件。spa

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
        )
    }
}

組件初始化的過程當中會進入if判斷語句,執行initInternalComponent()prototype

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
  }
}

4、總結

因爲全局註冊的組件是將組件的構造函數擴展到了Vue.options.components上,而組件在初始化的時候都會將自身optionsVue.options合併,擴展到當前組件的vm.$options.components下,因此全局組件能在任意組件被使用。而局部註冊的組件是將組件的構造函數擴展到了當前組件的vm.$options.components下,因此只能在當前組件使用。

相關文章
相關標籤/搜索