本篇文章从最简单的状况入手,不考虑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时完成的。
写到这里已经对组件机制有了初步的认识,数据的传递、父子组件通讯本文并无涉及,留到之后再看。