// 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 (typeofCtor !== '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. returncreateAsyncPlaceholder( 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 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 = newVNode( `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)) { returnrenderRecyclableComponentTemplate(vnode) }
return vnode }
可以看到,createComponent 的逻辑也会有一些复杂,但是分析源码比较推荐的是只分析核心流程,分支流程可以之后针对性的看,所以这里针对组件渲染这个 case 主要就 3 个关键步骤:
构造子类构造函数,安装组件钩子函数和实例化 vnode。
构造子类构造函数
1 2 3 4 5 6
const baseCtor = context.$options._base
// plain options object: turn it into a constructor if (isObject(Ctor)) { Ctor = baseCtor.extend(Ctor) }
// this is used to identify the "base" constructor to extend all plain-object // components with in Weex's multi-instance scenarios. Vue.options._base = Vue
// 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) }
// 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 returnSub }
Vue.extend 的作用就是构造一个 Vue 的子类,它使用一种非常经典的原型继承的方式把一个纯对象转换一个继承于 Vue 的构造器 Sub 并返回,然后对 Sub 这个对象本身扩展了一些属性,如扩展 options、添加全局 API 等;并且对配置中的 props 和 computed 做了初始化工作;最后对于这个 Sub 构造函数做了缓存,避免多次执行 Vue.extend 的时候对同一个子组件重复构造。
这样当我们去实例化 Sub 的时候,就会执行 this._init 逻辑再次走到了 Vue 实例的初始化逻辑,实例化子组件的逻辑在之后的章节会介绍。
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 */) } } } }
functionmergeHook (f1: any, f2: any): Function { constmerged = (a, b) => { // flow complains about extra args which is why we use any f1(a, b) f2(a, b) } merged._merged = true return merged }