Vue
的其中一個核心思想爲組件化,將頁面拆分紅不一樣的組件,獨立了資源,利於開發和維護。前面講了整個Vue
的實例和掛載,但並無詳細記錄子組件是怎麼開始一輪生命週期的。vue
createComponent
回顧一下vnode
的建立的過程:node
createElement
_createElement
createComponent
// 在 src/core/vdom/create-component.js 中: export function createComponent ( Ctor: Class<Component> | Function | Object | void, data: ?VNodeData, context: Component, children: ?Array<VNode>, tag?: string ): VNode | Array<VNode> | void { const baseCtor = context.$options._base // plain options object: turn it into a constructor if (isObject(Ctor)) { Ctor = baseCtor.extend(Ctor) } // extract props const propsData = extractPropsFromVNodeData(data, Ctor, tag) // install component management hooks onto the placeholder node installComponentHooks(data) // return a placeholder vnode const name = Ctor.options.name || tag const vnode = new VNode( `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`, data, undefined, undefined, undefined, context, { Ctor, propsData, listeners, tag, children }, asyncFactory ) return vnode }
核心流程大體分爲4步:react
Ctor(Vue子類構造函數)
props
vnode
Ctor
const baseCtor = context.$options._base // plain options object: turn it into a constructor if (isObject(Ctor)) { Ctor = baseCtor.extend(Ctor) }
這裏的關鍵在於弄清baseCtor
是什麼:vue-router
initGlobalAPI
中,咱們定義了 Vue.options._base = Vue
。在 Vue.prototype._init
中,將Vue.options
與options
進行了合併。dom
vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm )
因此這裏的baseCtor
就是Vue
自己,而這裏至關於執行了Vue.extend(Ctor)
async
Vue.extend
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 name = extendOptions.name || Super.options.name if (process.env.NODE_ENV !== 'production' && name) { validateComponentName(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 // For props and computed properties, we define the proxy getters on // the Vue instances at extension time, on the extended prototype. This // avoids Object.defineProperty calls for each instance created. if (Sub.options.props) { initProps(Sub) } if (Sub.options.computed) { initComputed(Sub) } // allow further extension/mixin/plugin usage Sub.extend = Super.extend Sub.mixin = Super.mixin Sub.use = Super.use // create asset registers, so extended classes // can have their private assets too. ASSET_TYPES.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 Sub.sealedOptions = extend({}, Sub.options) // cache constructor cachedCtors[SuperId] = Sub return Sub } }
這裏用了經典原型繼承的方式,構造了一個Vue
的子類Sub
,當實例化Sub
的時候,就會調用_init
方法,從新走到組件初始化建立的邏輯。函數
installComponentHooks
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 } } }
遍歷hooksToMerge
,不斷向data.hook
插入componentVNodeHooks
對象中對應的鉤子函數,包括init
、prepatch
、insert
、destory
。
這一步就是安裝組件鉤子函數,等待patch
過程時去執行。組件化
const componentVNodeHooks = { init (vnode: VNodeWithData, hydrating: boolean): ?boolean { if ( vnode.componentInstance && !vnode.componentInstance._isDestroyed && vnode.data.keepAlive ) { // kept-alive components, treat as a patch const mountedNode: any = vnode // work around flow componentVNodeHooks.prepatch(mountedNode, mountedNode) } else { const child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance ) child.$mount(hydrating ? vnode.elm : undefined, hydrating) } }, prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) { const options = vnode.componentOptions const child = vnode.componentInstance = oldVnode.componentInstance updateChildComponent( child, options.propsData, // updated props options.listeners, // updated listeners vnode, // new parent vnode options.children // new children ) }, insert (vnode: MountedComponentVNode) { const { context, componentInstance } = vnode if (!componentInstance._isMounted) { componentInstance._isMounted = true callHook(componentInstance, 'mounted') } if (vnode.data.keepAlive) { if (context._isMounted) { // vue-router#1212 // During updates, a kept-alive component's child components may // change, so directly walking the tree here may call activated hooks // on incorrect children. Instead we push them into a queue which will // be processed after the whole patch process ended. queueActivatedComponent(componentInstance) } else { activateChildComponent(componentInstance, true /* direct */) } } }, destroy (vnode: MountedComponentVNode) { const { componentInstance } = vnode if (!componentInstance._isDestroyed) { if (!vnode.data.keepAlive) { componentInstance.$destroy() } else { deactivateChildComponent(componentInstance, true /* direct */) } } } }
vnode
const name = Ctor.options.name || tag const vnode = new VNode( `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`, data, undefined, undefined, undefined, context, { Ctor, propsData, listeners, tag, children }, asyncFactory ) return vnode
最終生成的vnode
對象。性能
patch
在createElm
的實現中,有下面這個判斷:this
function createElm ( vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index ) { // ... if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) { return } // ... }
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) { let i = vnode.data if (isDef(i)) { const isReactivated = isDef(vnode.componentInstance) && i.keepAlive if (isDef(i = i.hook) && isDef(i = i.init)) { 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 } } }
let i = vnode.data
若是 i
有定義,則說明vnode
是一個組件,最後i
通過一系列的賦值指向了data.hook.init
,而後執行 i(vnode, false)
,也就是執行了上面提到過的init
鉤子函數。
init (vnode: VNodeWithData, hydrating: boolean): ?boolean { if ( vnode.componentInstance && !vnode.componentInstance._isDestroyed && vnode.data.keepAlive ) { // kept-alive components, treat as a patch const mountedNode: any = vnode // work around flow componentVNodeHooks.prepatch(mountedNode, mountedNode) } else { const child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance ) child.$mount(hydrating ? vnode.elm : undefined, hydrating) } },
這裏的邏輯就是經過 createComponentInstanceForVnode
建立子組件實例,而後經過$mount
掛載子組件。
export function createComponentInstanceForVnode ( vnode: any, parent: any, ): Component { const options: InternalComponentOptions = { _isComponent: true, _parentVnode: vnode, parent } // ... return new vnode.componentOptions.Ctor(options) }
首先看vnode.componentOptions
,它是在new VNode()
實例化vnode
時,將Ctor
做爲參數傳入的,上面也提到了,它其實就是Vue
的子類構造器Sub
,因此這裏至關於在new Sub()
建立子組件實例。這裏用_isComponent
標識爲一個組件,它的用處是在_init()
的時候會採起不一樣方式處理options
。
Vue.prototype._init = function (options?: Object) { const vm: Component = this // merge options if (options && options._isComponent) { initInternalComponent(vm, options) } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } }
在前面關於Vue實例化的文章也提到過,Vue
的合併策略是很慢的,當註冊一個內部組件的時候不須要作特殊的處理,因此能夠直接初始化內部組件提高性能。
能夠注意到 child.$mount(hydrating ? vnode.elm : undefined, hydrating)
接管了子組件的掛載,又開始新的一輪render
、update
、patch
,不斷的遞歸,只到整顆樹掛載完畢爲止。因爲這種遞歸的關係,在進行 insert(parentElm, vnode.elm, refElm)
的時候,插入的順序是先子後父。因此其實也能夠得知了父子的生命週期是父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted
這樣的順序。