Vue.js 的另外一个核心思想是组件化。所谓组件化就是将页面拆分红多个组件,组件之间资源相互独立,组件能够复用,组件之间也能够嵌套。 接下来以Vue/CLI初始化的代码为例,分析一下Vue组件初始化的过程。vue
import Vue from 'vue'
import App from './App.vue'
new Vue({
render: h => h(App),
}).$mount('#app')
复制代码
本篇要从_createElement
方法提及(_createElement
-> createElement
-> $createElement
-> render
-> _render
),此处是 render 函数生成 vnode 过程当中差别出现的地方。node
export function _createElement ( context: Component, tag?: string | Class<Component> | Function | Object, data?: VNodeData, children?: any, normalizationType?: number ): VNode | Array<VNode> {
// 对children作normalization,最终统一形式[vnode, vnode, ...]
if (normalizationType === ALWAYS_NORMALIZE) {
children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
children = simpleNormalizeChildren(children)
}
let vnode, ns
// tag是字符串
if (typeof tag === 'string') {
// ...
} else {
// tag是组件
// direct component options / constructor
vnode = createComponent(tag, data, context, children)
}
if (Array.isArray(vnode)) {
return vnode
} else if (isDef(vnode)) {
if (isDef(ns)) applyNS(vnode, ns)
if (isDef(data)) registerDeepBindings(data)
return vnode
} else {
return createEmptyVNode()
}
}
复制代码
此处的tag
是一个组件对象,因此会进入else
逻辑,vnode
将由createComponent
方法生成(实际是生成一个占位 vnode )react
export function createComponent ( Ctor: Class<Component> | Function | Object | void, data: ?VNodeData, context: Component, children: ?Array<VNode>, tag?: string ): VNode | Array<VNode> | void {
// Vue
const baseCtor = context.$options._base
// plain options object: turn it into a constructor
if (isObject(Ctor)) {
Ctor = baseCtor.extend(Ctor)
}
// install component management hooks onto the placeholder node
installComponentHooks(data)
// return a placeholder vnode
const name = Ctor.options.name || tag
// args order: tag、data、children、text、elm、context、componentOptions、asyncFactory
const vnode = new VNode(
`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
data, undefined, undefined, undefined, context,
{ Ctor, propsData, listeners, tag, children },
asyncFactory
)
return vnode
}
复制代码
生成占位 vnode 过程当中,利用Vue.extend()
将tag
转化为子组件的构造器。并做为new Vnode()
的其中一个参数的属性传入,等待调用。还调用了下面提到的installComponentHooks
。app
Vue.extend = function (extendOptions: Object): Function {
extendOptions = extendOptions || {}
const Super = this
const SuperId = Super.cid
const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
if (cachedCtors[SuperId]) {
return cachedCtors[SuperId]
}
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
)
// ...此处省略多行代码
// cache constructor
cachedCtors[SuperId] = Sub
return Sub
}
复制代码
以上为Vue.extend
内部,实现了js的类的继承,并返回子类构造器。async
const componentVNodeHooks = {
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {// ...},
prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {// ...},
insert (vnode: MountedComponentVNode) {// ...},
destroy (vnode: MountedComponentVNode) {// ...}
}
const hooksToMerge = Object.keys(componentVNodeHooks)
function installComponentHooks (data: VNodeData) {
const hooks = data.hook || (data.hook = {})
for (let i = 0; i < hooksToMerge.length; i++) {
const key = hooksToMerge[i]
const existing = hooks[key]
const toMerge = componentVNodeHooks[key]
if (existing !== toMerge && !(existing && existing._merged)) {
hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge
}
}
}
复制代码
以上为installComponentHooks
内部,给data
上挂了一些钩子函数,其中就包括随后要调用的init
。函数
生成占位 vnode 以后,render 函数执行完毕。进入 _update 函数,一样会调用 patch ,以后调用到 createElm,在createElm
内部产生了区别。组件化
function createElm ( vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index ) {
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
}
复制代码
因为此处的 vnode 为组件 vnode ,所以在进入 if 判断的createComponent()
方法内部会返回 true ,createElm
到此结束。接下来进入createComponent
ui
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data
if (isDef(i)) {
if (isDef(i = i.hook) && isDef(i = i.init)) {
//调用init hook
i(vnode, false /* hydrating */)
}
// after calling the init hook, if the vnode is a child component
// it should've created a child instance and mounted it. the child
// component also has set the placeholder vnode's elm.
// in that case we can just return the element and be done.
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue)
insert(parentElm, vnode.elm, refElm)
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true
}
}
}
复制代码
在遍历 data 过程当中发现有 init 钩子函数,执行 init 。接下来进入init
,接下来的逻辑稍微有些复杂,走的稍微有些远。咱们先进入 init 一探究竟,等 init 执行结束以后再回到 createComponent 执行组件的插入逻辑。this
const componentVNodeHooks = {
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
const child = vnode.componentInstance = createComponentInstanceForVnode(vnode, activeInstance)
// 手动调用$mount方法
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
},
// ...
}
复制代码
在init
内部,执行createComponentInstanceForVnode(vnode, activeInstance)
,将生成的组件实例赋值给 child 变量,手动调用$mount
方法。spa
其中要说明两个参数。参数 vnode 为占位 vnode ,而 activeInstance 是在 initLifecycle 中定义,_update 中赋值,表示当前激活的组件实例(即当前 vm 实例)。
export function createComponentInstanceForVnode (vnode, parent) {
const options: InternalComponentOptions = {
_isComponent: true,
_parentVnode: vnode, //占位vnode
parent //当前vm实例
}
// ...
return new vnode.componentOptions.Ctor(options)
}
复制代码
代码new vnode.componentOptions.Ctor(options)
其实是调用了真实组件的构造器(上文在生成占位 vnode 时把子组件的构造器做为其中一个参数的属性传入),今后开始子组件的实例化。
Vue.prototype._init = function (options?: Object) {
if (options && options._isComponent) {
initInternalComponent(vm, options)
}
initLifecycle(vm)
//...
}
复制代码
此处_isComponent
为true,进入initInternalComponent
方法。
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
const opts = vm.$options = Object.create(vm.constructor.options)
const parentVnode = options._parentVnode //占位vnode
opts.parent = options.parent //当前vm实例(子组件的父级)
// ...
}
复制代码
在initInternalComponent
方法中,此时已经进入了子组件的实例化,注意,在函数中定义了子组件实例的 parent 赋值为父组件实例, parentVnode 赋值为以前的占位 vnode 。
export let activeInstance: any = null
export function initLifecycle (vm: Component) {
const options = vm.$options
let parent = options.parent
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
//这里是子组件初始化,因此vm是子组件实例
parent.$children.push(vm)
}
vm.$parent = parent
vm.$root = parent ? parent.$root : vm
}
复制代码
在initLifecycle
中,定义了父子组件关系。vm.$parent = vm.$options.parent
,options.parent.$children.push(vm)
。
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
const prevEl = vm.$el
const prevActiveInstance = activeInstance
// activeInstance在此赋值
activeInstance = vm
vm._vnode = vnode // 渲染vnode
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
// ...
}
复制代码
在_update
中,activeInstance
赋值为当前子组件的实例。并把以前的activeInstance
赋值给prevActiveInstance
。给vm._vnode
赋值为子组件的渲染 vnode 。接下来看子组件的 patch 过程。
function patch (oldVnode, vnode, hydrating, removeOnly) {
// ...
if (isUndef(oldVnode)) {
// empty mount (likely as component), create new root element
isInitialPatch = true
createElm(vnode, insertedVnodeQueue)
}
return vnode.elm
}
复制代码
调用vm.__patch__(vm.$el, vnode, hydrating, false)
此处的 oldVnode 是 undefined,进入 if 判断。
function createElm ( vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index ) {
if (createComponent(vnode,insertedVnodeQueue,parentElm,refElm)) {
return
}
const data = vnode.data
const children = vnode.children
const tag = vnode.tag
if (isDef(tag)) {
// 建立DOM
vnode.elm = nodeOps.createElement(tag, vnode)
// 建立children
createChildren(vnode, children, insertedVnodeQueue)
// 插入(parentElm为空时不作插入操做)
insert(parentElm, vnode.elm, refElm)
} else if (isTrue(vnode.isComment)) {
vnode.elm = nodeOps.createComment(vnode.text)
insert(parentElm, vnode.elm, refElm)
} else {
vnode.elm = nodeOps.createTextNode(vnode.text)
insert(parentElm, vnode.elm, refElm)
}
}
复制代码
以上函数中,执行了createChildren
,其实就是若是有子节点,则递归地进行以前的流程建立子节点,并插入父节点。等待全部子节点都挂载完毕后,返回到上面的createComponent
,此时componentInstance
为 true,执行 initComponent
,执行组件的插入。到此为止,整个组件的初始化结束。
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data
if (isDef(i)) {
if (isDef(i = i.hook) && isDef(i = i.init)) {
//调用init hook
i(vnode, false /* hydrating */)
}
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue)
// 组件的插入在这儿
insert(parentElm, vnode.elm, refElm)
return true
}
}
}
复制代码
function initComponent (vnode, insertedVnodeQueue) {
// ...
vnode.elm = vnode.componentInstance.$el
// ...
}
复制代码
综上发现,嵌套组件的挂载顺序为子组件先挂载,父组件后挂载。