本篇文章從最簡單的狀況入手,不考慮prop和組件間通訊。html
vue文檔告訴咱們可使用Vue.component(tagName, options)
註冊一個組件vue
Vue.component('my-component', { // 選項 })
毫無疑問這是一個全局API,咱們順着代碼最終能夠找到Vue.component
是這樣的node
Vue.component = function(id, definition) { definition.name = definition.name || id definition = Vue.extend(definition) this.options[type + 's'][id] = definition return definition }
Vue.component
其實是Vue.extend
的封裝,Vue.extend
以下:app
Vue.extend = function (extendOptions: Object): Function { extendOptions = extendOptions || {} const Super = this const isFirstExtend = Super.cid === 0 if (isFirstExtend && extendOptions._Ctor) { return extendOptions._Ctor } let name = extendOptions.name || Super.options.name const Sub = function VueComponent (options) { this._init(options) } Sub.prototype = Object.create(Super.prototype) Sub.prototype.constructor = Sub Sub.cid = cid++ Sub.options = mergeOptions( Super.options, extendOptions ) Sub['super'] = Super // allow further extension Sub.extend = Super.extend // create asset registers, so extended classes // can have their private assets too. config._assetTypes.forEach(function (type) { Sub[type] = Super[type] }) // enable recursive self-lookup if (name) { Sub.options.components[name] = Sub } // keep a reference to the super options at extension time. // later at instantiation we can check if Super's options have // been updated. Sub.superOptions = Super.options Sub.extendOptions = extendOptions // cache constructor if (isFirstExtend) { extendOptions._Ctor = Sub } return Sub }
能夠看到Vue.extend
返回的其實是一個構造函數Sub
,而且此構造函數繼承自Vue。裏面有這麼幾行代碼dom
Sub.options = mergeOptions( Super.options, extendOptions )
那麼Super.options(即Vue.options
)是什麼呢?函數
Vue.options = Object.create(null) // 包含components directives filters config._assetTypes.forEach(type => { Vue.options[type + 's'] = Object.create(null) }) util.extend(Vue.options.components, builtInComponents)
Vue.options事實上存放了系統以及用戶定義的component、directive、filter,builtInComponents
爲Vue內置的組件(如keep-alive
),打印看下:oop
因此Sub構造函數的options不只包含components、directives、filters,還包含預先定義的實例化時所需的選項。定義一個組件以下:學習
let MyComponent = Vue.extend({ data() { return { msg: "this's compoennt" } }, render(h) { return h('p', this.msg) } })
打印MyComponent.options以下:ui
再回過頭看Vue.component
,能夠發現他作的工做就是擴展一個Vue構造函數(VueComponent
),並將這個構造函數(VueComponent
)添加到Vue.options.components this
如今咱們已經能夠回答最開始的問題---vue的組件是什麼?vue的組件其實就是擴展的Vue構造函數,而且在適當的時候實例化爲Vue實例。
組件對應的vnode是什麼樣子?從一個簡單的例子入手:
let MyComponent = Vue.component('my-component', { data() { return { msg: "this's component" } }, render(h) { return h('p', this.msg) } }) window.app = new Vue({ render(h) { return h('my-component') } }).$mount('#root')
上篇文章已經說道在initRender的時候會初始一個系統watcher,以下:
vm._watcher = new Watcher(vm, () => { vm._update(vm._render(), hydrating) }, noop)
上篇文章提到vm._render()
返回的是一個虛擬dom(vnode
),具體到本篇,那麼組件標籤會被解析成什麼樣的虛擬節點呢?
事實上render的時候會首先調用createElement
,根據傳入的tag(html標籤或者組件標籤)不一樣,vnode能夠分爲如下兩種:
這種就是普通的html標籤(p、div、span等)對應的vnode
當tag是組件標籤的時候,會調用createComponent
,以下:
else if ((Ctor = resolveAsset(context.$options, 'components', tag))) { // component return createComponent(Ctor, data, context, children, tag) }
這裏的Ctor就是咱們擴展的組件構造函數,createComponent
最終返回的vnode以下:
const vnode = new VNode( `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`, data, undefined, undefined, undefined, undefined, context, { Ctor, propsData, listeners, tag, children } )
須要注意的是data有一個操做:
// merge component management hooks onto the placeholder node mergeHooks(data)
merge以後data.hook
會添加四個方法:
前文看到組件構造函數其實是存在組件對應vnode的componentOptions中,那麼到底是何時實例化組件呢?
順着vm._update(vm._render(), hydrating)往下看發現最終調用的是patch操做,而對於組件實例化而言並不存在與之對應的oldVnode(由於oldVnode是在組件更新後產生的),因此最終的邏輯歸到根據組件對應的vnode建立真實dom節點
,即
createElm(vnode, insertedVnodeQueue)
咱們還記得組件的構造函數是vnode.componentOptions.Ctor
,其實最終調用的也是這個構造函數。
createElm函數中與組件初始化相關的關鍵代碼以下:
const data = vnode.data if (isDef(data)) { if (isDef(i = data.hook) && isDef(i = i.init)) i(vnode) if (isDef(i = vnode.child)) { initComponent(vnode, insertedVnodeQueue) return vnode.elm } }
init的代碼以下:
function init (vnode: VNodeWithData, hydrating: boolean) { if (!vnode.child || vnode.child._isDestroyed) { const child = vnode.child = createComponentInstanceForVnode(vnode, activeInstance) child.$mount(hydrating ? vnode.elm : undefined, hydrating) } } export function createComponentInstanceForVnode ( vnode: any, // we know it's MountedComponentVNode but flow doesn't parent: any // activeInstance in lifecycle state ): Component { const vnodeComponentOptions = vnode.componentOptions const options: InternalComponentOptions = { _isComponent: true, parent, propsData: vnodeComponentOptions.propsData, _componentTag: vnodeComponentOptions.tag, _parentVnode: vnode, _parentListeners: vnodeComponentOptions.listeners, _renderChildren: vnodeComponentOptions.children } // check inline-template render functions const inlineTemplate = vnode.data.inlineTemplate if (inlineTemplate) { options.render = inlineTemplate.render options.staticRenderFns = inlineTemplate.staticRenderFns } return new vnodeComponentOptions.Ctor(options) }
通過init以後能夠看到組件vnode.child
對應的就是組件的實例,且child.$el
即爲組件對應的真實dom,可是實際上createElm返回的是vnode.elm,怎麼回事?事實上initComponent
中作了處理
vnode.elm = vnode.child.$el
綜上,組件實例化是在由虛擬dom映射爲真實dom時完成的。
寫到這裏已經對組件機制有了初步的認識,數據的傳遞、父子組件通訊本文並無涉及,留到之後再看。