vue 元件原理及建立過程,
之前我們提到,當vue再掛載到真實dom元素上時,會執行所掛載vue物件的render函式,render函式主要內容就是執行 $creatElement返回一個vdom,而如果我們碰到的render函式的tag並不是一個普通標籤,而是一個元件時,會執行createComponent 方法,建立一個元件vdom,這裡面的邏輯是和其他vdom的建立非常不一樣的,,我們進入程式碼
export function createComponent ( Ctor: Class<Component> | Function | Object | void, data: ?VNodeData, context: Component, children: ?Array<VNode>, tag?: string ): VNode | Array<VNode> | void { if (isUndef(Ctor)) { return } const baseCtor = context.$options._base // plain options object: turn it into a constructor if (isObject(Ctor)) { Ctor = baseCtor.extend(Ctor) } // if at this stage it's not a constructor or an async component factory, // reject. if (typeof Ctor !== 'function') { if (process.env.NODE_ENV !== 'production') { warn(`Invalid Component definition: ${String(Ctor)}`, context) } return } // async component let asyncFactory if (isUndef(Ctor.cid)) { asyncFactory = Ctor Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context) if (Ctor === undefined) { // return a placeholder node for async component, which is rendered // as a comment node but preserves all the raw information for the node. // the information will be used for async server-rendering and hydration. return createAsyncPlaceholder( asyncFactory, data, context, children, tag ) } } data = data || {} // resolve constructor options in case global mixins are applied after // component constructor creation resolveConstructorOptions(Ctor) // transform component v-model data into props & events if (isDef(data.model)) { transformModel(Ctor.options, data) } // extract props const propsData = extractPropsFromVNodeData(data, Ctor, tag) // functional component if (isTrue(Ctor.options.functional)) { return createFunctionalComponent(Ctor, propsData, data, context, children) } // extract listeners, since these needs to be treated as // child component listeners instead of DOM listeners const listeners = data.on // replace with listeners with .native modifier // so it gets processed during parent component patch. data.on = data.nativeOn if (isTrue(Ctor.options.abstract)) { // abstract components do not keep anything // other than props & listeners & slot // work around flow const slot = data.slot data = {} if (slot) { data.slot = slot } } // 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 ) // Weex specific: invoke recycle-list optimized @render function for // extracting cell-slot template. // https://github.com/Hanks10100/weex-native-directive/tree/master/component /* istanbul ignore if */ if (__WEEX__ && isRecyclableComponent(vnode)) { return renderRecyclableComponentTemplate(vnode) } return vnode }
根據flow的定義我們可以看到這個函式會返回一個vdom或者vdom陣列或者空,函式中有一個定義const baseCtor = context.$options._base
,這個其實就是vm本身,然後執行 extend方法,這也是在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.prototype=object.create(super.prototype)
Sub ,
重新回到createComponent中,中間有一些非同步元件的邏輯判斷,對v-model的判斷,對hooks的新增,最後返回一個component 型別的vnode,然後重新回到 之前提到的_update,進入裡面的patch方法,,patch中對元件的vnode也有不同的處理,會判斷vnode 是否時component型別,如果是的話,經過一些步驟,最後在init hooks中,,會執行子元件構造器的init(繼承於vue )方法,init 方法重新回到最初始的init方法,
Vue.prototype._init = function (options?: Object) { const vm: Component = this // a uid vm._uid = uid++ let startTag, endTag /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { startTag = `vue-perf-start:${vm._uid}` endTag = `vue-perf-end:${vm._uid}` mark(startTag) } // a flag to avoid this being observed vm._isVue = true // merge options if (options && options._isComponent) { // optimize internal component instantiation // since dynamic options merging is pretty slow, and none of the // internal component options needs special treatment. initInternalComponent(vm, options) } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ) } /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { initProxy(vm) } else { vm._renderProxy = vm } // expose real self vm._self = vm initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created') /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { vm._name = formatComponentName(vm, false) mark(endTag) measure(`vue ${vm._name} init`, startTag, endTag) } if (vm.$options.el) { vm.$mount(vm.$options.el) } }
這裡注意一下initlifecycle,這裡會儲存一下當前元件的父元件,從而能夠構造出父子關係,然後這裡因為子元件的option沒有el,$mount 不會在這裡執行,會在上一個函式執行子建構函式的mount方法,然後又回到之前的mountComponent ,new Watcher(),updateComponent,然後執行子元件的render函式,update,子元件的patch,creatElem,注意,這裡不會執行insert方法,真正的執行真實dom樹插入,而是停在dom樹建立完畢的情況,當然,如果在creatElem 過程中如果又碰到元件還會執行這一整個元件的處理流程,通過這樣一個深度優先遍歷 整個巢狀元件的dom tree便會完美額構建完成,最後一次性插入到真實dom中