组件(Component
)是Vue
一个重要的功能。一般用于包装可重用代码,或者是定义高级组件。对于小型项目,(可能)会增强代码的结构性,但是并非必须。对于稍大型的项目,它则是模块化的基础,也是代码拆分常用的形式。
组件有很多内容,比如组件消息传递、数据传递、slot
、异步组件等等,作为一个有品味的博主,以上内容本篇统统不涉及。本篇只从一个注册+创建角度说一下组件是怎么一步一步放到页面上的。
基础示例代码(来自Vue
官方文档)
// 注册 Vue.component('my-component', { template: '
A custom component!
' }) // 创建根实例 new Vue({ el: '#example' })
整体来说,组件使用需要经过两个步骤:声明和实例化。声明即把组件的显式声明内容,处理为配置项,挂载到实例上。接着根据模板解析的流程,在不同的地方根据配置初始化为一个一个的组件实例。
组件声明
组件通常使用全局注册或者局部注册,二者没有本质区别(吧)。我们从全局注册角度说一下
Vue.component(name, definition) // name为'my-component',definition是配置项 -Sub = Vue.extend(definition) -Vue.options['components']['my-component'] = Sub
子组件(Sub)结构
组件渲染(实例化)
上篇(Vue-初始化和渲染过程)讲到,渲染是从initRender
开始的,当执行到createElement
时,会分为不同类型的节点来处理。这里对此操作展开叙述。
重述一下前面的流程
-initRender -Vue._mount -vm._render -createElm // 生成VNode节点
createElm
这里会分为组件、浏览器tag
、comment
、文本分别处理对应的element
。Vue挂载的根组件只能为组件或者tag
,不能为comment
或者文本。
另外,在Vue
模板解析(initRender
)时,不区分是否为组件的。所有元素都在createElement
函数内处理,最后都会生成一个模板函数
_h(tagName, {...})
实际区分在从组件的父节点调用createElm
,来生成VNode
的时候。为不打断流程,组件VNode
相关放在最后说。
如果是组件,会进行组件初始化(init
)流程:
init
负责会按层次处理好子节点
-vnode.child = createComponentInstanceForVnode -vnode.child.$mount =Vue.prototype._mount
按照最外层vm实例初始化的步骤,再走一边组件的初始化
vm._render // 这里vm是vnode.child,即VueComponent实例 -vm._update -vm.$el = vm.__patch__
基本流程图如下
组件渲染过程
这是一个递归调用,直到节点不为组件(浏览器tag
、comments
、text
三种),返回创建的基础elm元素。当节点的children
为空时,会返回父级创建的浏览器tag
元素。
生成的元素被放入vnode.elm
中。
组件VNode
相关
-createElm -createElement -createComponent
如果是组件,会先返回一个占位vnode
,当将组件的所有子节点按层次处理完之后,再把child
(此时已经为可用的tag
)赋值给vnode.elm
,这样这个组件就可以使用了。
VNode结构
这个占位vnode
,除默认字段外,只设置了tag、data、context、componentOptions
// tag ("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')) // 如:vue-component-1-button-counter
// data {hook: init, insert, prepatch, destroy} // 组件操作入口
// componentOptions { Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children }
createElement
判断定义了data.hook
函数,就执行组件实例初始化和挂载,这是个基础流程,和Vue
初始化的一致。