摘要:定义一个组件如下打印如下再回过头看,可以发现他做的工作就是扩展一个构造函数,并将这个构造函数添加到现在我们已经可以回答最开始的问题的组件是什么的组件其实就是扩展的构造函数,并且在适当的时候实例化为实例。
vue@2.0源码学习---组件究竟是什么
本篇文章从最简单的情况入手,不考虑prop和组件间通信。
Vue.componentvue文档告诉我们可以使用Vue.component(tagName, options)注册一个组件
Vue.component("my-component", { // 选项 })
毫无疑问这是一个全局API,我们顺着代码最终可以找到Vue.component是这样的
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如下:
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。里面有这么几行代码
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),打印看下:
所以Sub构造函数的options不仅包含components、directives、filters,还包含预先定义的实例化时所需的选项。定义一个组件如下:
let MyComponent = Vue.extend({ data() { return { msg: "this"s compoennt" } }, render(h) { return h("p", this.msg) } })
打印MyComponent.options如下:
再回过头看Vue.component,可以发现他做的工作就是扩展一个Vue构造函数(VueComponent),并将这个构造函数(VueComponent)添加到Vue.options.components
现在我们已经可以回答最开始的问题---vue的组件是什么?vue的组件其实就是扩展的Vue构造函数,并且在适当的时候实例化为Vue实例。
组件对应的vnode组件对应的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可以分为以下两种:
platform built-in elements
这种就是普通的html标签(p、div、span等)对应的vnode
component
当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会添加四个方法:
init 实例化组件时调用
prepatch patch之前调用
insert 真实节点插入时调用
destory 组件实例销毁时调用
实例化组件前文看到组件构造函数实际上是存在组件对应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时完成的。
写到这里已经对组件机制有了初步的认识,数据的传递、父子组件通信本文并没有涉及,留到以后再看。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/90375.html
摘要:特意对前端学习资源做一个汇总,方便自己学习查阅参考,和好友们共同进步。 特意对前端学习资源做一个汇总,方便自己学习查阅参考,和好友们共同进步。 本以为自己收藏的站点多,可以很快搞定,没想到一入汇总深似海。还有很多不足&遗漏的地方,欢迎补充。有错误的地方,还请斧正... 托管: welcome to git,欢迎交流,感谢star 有好友反应和斧正,会及时更新,平时业务工作时也会不定期更...
摘要:写文章不容易,点个赞呗兄弟专注源码分享,文章分为白话版和源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基于版本如果你觉得排版难看,请点击下面链接或者拉到下面关注公众号也可以吧原理白话版从模板上使用到挂载到页面 写文章不容易,点个赞呗兄弟专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基于...
摘要:前言这节净是些唠叨,只想看升级的可直接跳过。在不久之前,如约发布了版本。正如计划之初,博客的版本也将升级到。升级之旅首先,升级依赖。那该怎么做哪再一次谷哥和查阅文档,然而一无所获。返回的是整个项目路由的实例,它是只读的。 Troubleshooting of upgrading Vue from 1.0 to 2.0 系列文章: Vue 2.0 升(cai)级(keng)之旅 (本...
阅读 3139·2021-11-22 14:45
阅读 3269·2019-08-29 13:11
阅读 2280·2019-08-29 12:31
阅读 895·2019-08-29 11:21
阅读 2974·2019-08-29 11:09
阅读 3599·2019-08-28 18:11
阅读 1385·2019-08-26 13:58
阅读 1258·2019-08-26 13:27