_createElement方法中有对参数tag的判断,如果是一个普通的html标签,则会实例化一个普通VNode节点,否则通过createComponent方法创建一个组件VNode
export function _createElement (context: Component, // 上下文环境tag?: string | Class<Component> | Function | Object, // 标签data?: VNodeData, // VNode数据children?: any, // 当前VNode的子节点 需规范为标准的VNode数组normalizationType?: number // 子节点规范的类型 参考render函数是编译生成还是用户手写): VNode | Array<VNode> {if (isDef(data) && isDef((data: any).__ob__)) {// 为了不影响看代码 先注释了// process.env.NODE_ENV !== 'production' && warn(// `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +// 'Always create fresh vnode data objects in each render!',// context// )return createEmptyVNode()}// object syntax in v-bindif (isDef(data) && isDef(data.is)) {tag = data.is}if (!tag) {// in case of component :is set to falsy valuereturn createEmptyVNode()}// warn against non-primitive keyif (process.env.NODE_ENV !== 'production' &&isDef(data) && isDef(data.key) && !isPrimitive(data.key)) {if (!__WEEX__ || !('@binding' in data.key)) {warn('Avoid using non-primitive value as key, ' +'use string/number value instead.',context)}}// support single function children as default scoped slotif (Array.isArray(children) &&typeof children[0] === 'function') {data = data || {}data.scopedSlots = { default: children[0] }children.length = 0}if (normalizationType === ALWAYS_NORMALIZE) {children = normalizeChildren(children)} else if (normalizationType === SIMPLE_NORMALIZE) {children = simpleNormalizeChildren(children)}let vnode, nsif (typeof tag === 'string') {let Ctorns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)if (config.isReservedTag(tag)) {// platform built-in elementsif (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn) && data.tag !== 'component') {warn(`The .native modifier for v-on is only valid on components but it was used on <${tag}>.`,context)}vnode = new VNode(config.parsePlatformTagName(tag), data, children,undefined, undefined, context)} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {// componentvnode = createComponent(Ctor, data, context, children, tag)} else {// unknown or unlisted namespaced elements// check at runtime because it may get assigned a namespace when its// parent normalizes childrenvnode = new VNode(tag, data, children,undefined, undefined, context)}} else {// direct component options / constructor// 本质上是一个component类型,会直接通过createComponent方法创建vnodevnode = 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()}}
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 {if (isUndef(Ctor)) {return}const baseCtor = context.$options._base// plain options object: turn it into a constructorif (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 componentlet asyncFactoryif (isUndef(Ctor.cid)) {asyncFactory = CtorCtor = resolveAsyncComponent(asyncFactory, baseCtor)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 creationresolveConstructorOptions(Ctor)// transform component v-model data into props & eventsif (isDef(data.model)) {transformModel(Ctor.options, data)}// extract propsconst propsData = extractPropsFromVNodeData(data, Ctor, tag)// functional componentif (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 listenersconst listeners = data.on// replace with listeners with .native modifier// so it gets processed during parent component patch.data.on = data.nativeOnif (isTrue(Ctor.options.abstract)) {// abstract components do not keep anything// other than props & listeners & slot// work around flowconst slot = data.slotdata = {}if (slot) {data.slot = slot}}// install component management hooks onto the placeholder nodeinstallComponentHooks(data)// return a placeholder vnodeconst name = Ctor.options.name || tagconst 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}
createComponent实现流程
构造子类构造函数
const baseCtor = context.$options._base// plain options object: turn it into a constructorif (isObject(Ctor)) {Ctor = baseCtor.extend(Ctor)}
在编写一个组件时通常都是创建一个普通对象
import HelloWorld from './components/HelloWorld'export default {name: 'app',components: {HelloWorld}}
这个例子中export的是一个对象,createComponent代码逻辑会执行到Ctor = baseCtor.extend(Ctor)
baseCtor实际上就是Vue,这个定义在最开始初始化Vue的阶段
在src/core/global-api/index.js 中initGlobalAPI 方法中有这样一段逻辑
// 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
这里定义的是Vue.options
context.$options实际上在src/core/instance/init.js 里 Vue 原型上的 _init 函数中有这么一段逻辑
// 把Vue构造函数的options和用户传入的options做一层合并,到vm.$options上vm.$options = mergeOptions(resolveConstructorOptions(vm.constructor),options || {},vm)
把Vue上的一些option扩展到了vm.$options上,所以通过vm.$options._base就能拿到Vue这个构造函数了
Vue.extend函数
作用是构造一个Vue的子类
定义在 src/core/global-api/extend.js 中
Vue.extend = function (extendOptions: Object): Function {extendOptions = extendOptions || {}const Super = this // Vueconst SuperId = Super.cidconst cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})if (cachedCtors[SuperId]) {return cachedCtors[SuperId]}const name = extendOptions.name || Super.options.nameif (process.env.NODE_ENV !== 'production' && name) {validateComponentName(name)}// 实例化Sub时就会执行this._init再次走到Vue实例的初始化逻辑const Sub = function VueComponent (options) {this._init(options)}// 原型继承的方式把一个纯对象转换一个继承于Vue的构造器Sub并返回Sub.prototype = Object.create(Super.prototype)Sub.prototype.constructor = SubSub.cid = cid++// 对Sub这个对象本身扩展了一些属性// 扩展optionsSub.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.// 对配置中的props和computed做初始化if (Sub.options.props) {initProps(Sub)}if (Sub.options.computed) {initComputed(Sub)}// allow further extension/mixin/plugin usage// 添加全局APISub.extend = Super.extendSub.mixin = Super.mixinSub.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-lookupif (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.optionsSub.extendOptions = extendOptionsSub.sealedOptions = extend({}, Sub.options)// cache constructor// 对Sub构造函数做了缓存,避免多次执行Vue.extend时对同一个子组件重复构造cachedCtors[SuperId] = Subreturn Sub}
安装组件钩子函数
// install component management hooks onto the placeholder nodeinstallComponentHooks(data)
在初始化一个Component类型的VNode的过程中实现了几个钩子函数
// inline hooks to be invoked on component VNodes during patchconst componentVNodeHooks = {init (vnode: VNodeWithData, hydrating: boolean): ?boolean {if (vnode.componentInstance &&!vnode.componentInstance._isDestroyed &&vnode.data.keepAlive) {// kept-alive components, treat as a patchconst mountedNode: any = vnode // work around flowcomponentVNodeHooks.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.componentOptionsconst child = vnode.componentInstance = oldVnode.componentInstanceupdateChildComponent(child,options.propsData, // updated propsoptions.listeners, // updated listenersvnode, // new parent vnodeoptions.children // new children)},insert (vnode: MountedComponentVNode) {const { context, componentInstance } = vnodeif (!componentInstance._isMounted) {componentInstance._isMounted = truecallHook(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 } = vnodeif (!componentInstance._isDestroyed) {if (!vnode.data.keepAlive) {componentInstance.$destroy()} else {deactivateChildComponent(componentInstance, true /* direct */)}}}}const hooksToMerge = Object.keys(componentVNodeHooks)// 把componentVNodeHooks的钩子函数合并到data.hook中,在VNode执行patch的过程中执行相关的钩子函数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]// 合并策略// 如果某个时机的钩子已经存在data.hook中,就通过执行mergeHook函数做合并if (existing !== toMerge && !(existing && existing._merged)) {hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge}}}function mergeHook (f1: any, f2: any): Function {// 做合并-在最终执行时依次执行这两个钩子函数const merged = (a, b) => {// flow complains about extra args which is why we use anyf1(a, b)f2(a, b)}merged._merged = truereturn merged}
实例化 VNode
// return a placeholder vnodeconst name = Ctor.options.name || tag// 实例化一个VNodeconst vnode = new VNode(`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,data, undefined, undefined, undefined, context,{ Ctor, propsData, listeners, tag, children },asyncFactory)// ...// 返回实例化的VNodereturn vnode
和普通元素节点的vnode不同的是,组件的vnode是没有children的
