createElement() 方法
文件位置:src\core\vdom\create-element.js
/* 生成组件或普通标签的 vnode,一个包装函数 wrapper function for providing a more flexible interface without getting yelled at by flow */ export function createElement ( context: Component, tag: any, data: any, children: any, normalizationType: any, alwaysNormalize: boolean ): VNode | Array<VNode> { if (Array.isArray(data) || isPrimitive(data)) { normalizationType = children children = data data = undefined } if (isTrue(alwaysNormalize)) { normalizationType = ALWAYS_NORMALIZE } // 执行 _createElement 方法创建组件的 VNode return _createElement(context, tag, data, children, normalizationType) } 复制代码
_createElement() 方法
文件位置:src\core\vdom\create-element.js
/** * 生成 vnode, * 1、平台保留标签和未知元素执行 new Vnode() 生成 vnode * 2、组件执行 createComponent 生成 vnode * 2.1 函数式组件执行自己的 render 函数生成 VNode * 2.2 普通组件则实例化一个 VNode,并且在其 data.hook 对象上设置 4 个方法, * 在组件的 patch 阶段会被调用,从而进入子组件的实例化、挂载阶段,直至完成渲染 * @param {*} context 上下文 * @param {*} tag 标签 * @param {*} data 属性 JSON 字符串 * @param {*} children 子节点数组 * @param {*} normalizationType 节点规范化类型 * @returns VNode or Array<VNode> */ export function _createElement ( context: Component, tag?: string | Class<Component> | Function | Object, data?: VNodeData, children?: any, normalizationType?: number ): 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 ) // 如果属性是一个响应式对象,则返回一个空节点的 VNode return createEmptyVNode() } // object syntax in v-bind if (isDef(data) && isDef(data.is)) { tag = data.is } if (!tag) { /* 动态组件的 is 属性是一个假值时,tag 为 false,则返回一个空节点的 VNode 如:<component :is="false"></component> in case of component :is set to falsy value */ return createEmptyVNode() } /* 检测唯一键 key,只能是 字符串 或者 数字 warn against non-primitive key */ if (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 slot */ if (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, ns if (typeof tag === 'string') { /* 标签是字符串时,该标签有三种可能: 1、平台保留标签 2、自定义组件 3、不知名标签 */ let Ctor // 命名空间 ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag) if (config.isReservedTag(tag)) { /* tag 是平台原生标签 platform built-in elements */ if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn) && data.tag !== 'component') { /* v-on 指令的 .native 只在组件上生效,如: <comp @click="outClick"></comp> --> 点击组件无法触发 outClick 方法 <comp @click.native="outClick"></comp> --> 点击组件可以触发 outClick 方法 */ warn( `The .native modifier for v-on is only valid on components but it was used on <${tag}>.`, context ) } // 实例化一个 VNode vnode = new VNode( config.parsePlatformTagName(tag), data, children, undefined, undefined, context ) } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) { /* tag 是一个自定义组件 - 在 this.$options.components 对象中找到指定标签名称的组件构造函数, 创建组件的 VNode - 函数式组件直接执行其 render 函数生成 VNode, - 普通组件则实例化一个 VNode,并且在其 data.hook 对象上设置了 4 个方法, 在组件的 patch 阶段会被调用 从而进入子组件的实例化、挂载阶段,直至完成渲染 component */ vnode = createComponent(Ctor, data, context, children, tag) } else { // 不知名的一个标签,但也生成 VNode,因为考虑到在运行时可能会给一个合适的名字空间 // unknown or unlisted namespaced elements // check at runtime because it may get assigned a namespace when its // parent normalizes children vnode = new VNode( tag, data, children, undefined, undefined, context ) } } else { /* tag 为非字符串,比如可能是一个组件的配置对象或者是一个组件的构造函数 direct component options / constructor */ vnode = createComponent(tag, data, context, children) } // 返回组件的 VNode 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
/** * 创建组件的 VNode, * 1、函数式组件通过执行其 render 方法生成组件的 VNode * 2、普通组件通过 new VNode() 生成其 VNode, * 但是普通组件有一个重要操作是在 data.hook 对象上设置了四个钩子函数, * 分别是 init、prepatch、insert、destroy,在组件的 patch 阶段会被调用, * 比如 init 方法,调用时会进入子组件实例的创建挂载阶段,直到完成渲染 * @param {*} Ctor 组件构造函数 * @param {*} data 属性组成的 JSON 字符串 * @param {*} context 上下文 * @param {*} children 子节点数组 * @param {*} tag 标签名 * @returns VNode or Array<VNode> */ export function createComponent ( Ctor: Class<Component> | Function | Object | void, data: ?VNodeData, context: Component, children: ?Array<VNode>, tag?: string ): VNode | Array<VNode> | void { // 组件构造函数不存在,直接结束,Ctor === undefined || Ctor === null if (isUndef(Ctor)) { return } // Vue.extend const baseCtor = context.$options._base // plain options object: turn it into a constructor // 当 Ctor 为配置对象时,通过 Vue.extend 将其转为构造函数 if (isObject(Ctor)) { Ctor = baseCtor.extend(Ctor) } // if at this stage it's not a constructor or an async component factory, // reject. // 如果到这个为止,Ctor 仍然不是一个函数,则表示这是一个无效的组件定义 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) if (Ctor === undefined) { /* 为异步组件返回一个占位符节点,组件被渲染为注释节点, 但保留了节点的所有原始信息,这些信息将用于 异步服务器渲染 和 hydration */ return createAsyncPlaceholder( asyncFactory, data, context, children, tag ) } } // 节点的属性 JSON 字符串 data = data || {} /* 这里其实就是组件做选项合并的地方,即编译器将组件编译为渲染函数, 渲染时执行 render 函数,然后执行其中的 _c,就会走到这里了 解析构造函数选项,并合基类选项,以防止在组件构造函数创建后应用全局混入 */ resolveConstructorOptions(Ctor) // transform component v-model data into props & events // 将组件的 v-model 的信息(值和回调)转换为 data.attrs 对象的属性、值和 data.on 对象上的事件、回调 if (isDef(data.model)) { transformModel(Ctor.options, data) } /* extract props 提取 props 数据,得到 propsData 对象,propsData[key] = val 以组件 props 配置中的属性为 key,父组件中对应的数据为 value */ const propsData = extractPropsFromVNodeData(data, Ctor, tag) // functional component —— 函数式组件 if (isTrue(Ctor.options.functional)) { /** 执行函数式组件的 render 函数生成组件的 VNode : 1、设置组件的 props 对象 2、设置函数式组件的渲染上下文,传递给函数式组件的 render 函数 3、调用函数式组件的 render 函数生成 vnode */ return createFunctionalComponent(Ctor, propsData, data, context, children) } // extract listeners, since these needs to be treated as // child component listeners instead of DOM listeners // 获取事件监听器对象 data.on,因为这些监听器需要作为子组件监听器处理,而不是 DOM 监听器 const listeners = data.on // replace with listeners with .native modifier // so it gets processed during parent component patch. // 将带有 .native 修饰符的事件对象赋值给 data.on data.on = data.nativeOn if (isTrue(Ctor.options.abstract)) { // 如果是抽象组件,则值保留 props、listeners 和 slot // abstract components do not keep anything // other than props & listeners & slot // work around flow const slot = data.slot data = {} if (slot) { data.slot = slot } } /** 在组件的 data 对象上设置 hook 对象, hook 对象增加四个属性,init、prepatch、insert、destroy, 负责组件的创建、更新、销毁,这些方法在组件的 patch 阶段会被调用 install component management hooks onto the placeholder node */ installComponentHooks(data) // return a placeholder vnode const name = Ctor.options.name || tag // 实例化组件的 VNode,对于普通组件的标签名会比较特殊,vue-component-${cid}-${name} 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 } 复制代码
resolveConstructorOptions() 方法
文件位置:src\core\vdom\create-component.js
// 从构造函数上解析配置对象 export function resolveConstructorOptions (Ctor: Class<Component>) { let options = Ctor.options // 如果构造函数的 super 属性存在,证明还有基类,此时需要递归进行处理 if (Ctor.super) { const superOptions = resolveConstructorOptions(Ctor.super) // 缓存 const cachedSuperOptions = Ctor.superOptions // 若缓存的配置和基类的配置不一致,说明配置已发生更改 if (superOptions !== cachedSuperOptions) { // super option changed, // need to resolve new options. Ctor.superOptions = superOptions // check if there are any late-modified/attached options (#4976) // 获取发生变更的配置项 const modifiedOptions = resolveModifiedOptions(Ctor) // update base extend options if (modifiedOptions) { // 将更改的配置项和 extent 选项进行合并 extend(Ctor.extendOptions, modifiedOptions) } // 将合并后得到的新配置赋值给 $options options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions) if (options.name) { options.components[options.name] = Ctor } } } return options } 复制代码
resolveModifiedOptions() 方法
文件位置:/src/core/instance/init.js
/** * 解析构造函数选项中后续被修改或者增加的选项 */ function resolveModifiedOptions (Ctor: Class<Component>): ?Object { let modified // 构造函数选项 const latest = Ctor.options // 密封的构造函数选项,备份 const sealed = Ctor.sealedOptions // 对比两个选项,记录不一致的选项 for (const key in latest) { if (latest[key] !== sealed[key]) { if (!modified) modified = {} modified[key] = latest[key] } } return modified } 复制代码
transformModel() 方法
文件位置:/src/core/vdom/create-component.js
/** * 将组件的 v-model 的信息(值和回调)转换为 data.attrs 对象的属性、值和 data.on 对象上的事件、回调 * transform component v-model info (value and callback) into * prop and event handler respectively. */ function transformModel (options, data: any) { // model 的属性和事件,默认为 value 和 input const prop = (options.model && options.model.prop) || 'value' const event = (options.model && options.model.event) || 'input' // 在 data.attrs 对象上存储 v-model 的值 ;(data.attrs || (data.attrs = {}))[prop] = data.model.value // 在 data.on 对象上存储 v-model 的事件 const on = data.on || (data.on = {}) // 已存在的事件回调函数 const existing = on[event] // v-model 中事件对应的回调函数 const callback = data.model.callback // 合并回调函数 if (isDef(existing)) { if ( Array.isArray(existing) ? existing.indexOf(callback) === -1 : existing !== callback ) { on[event] = [callback].concat(existing) } } else { on[event] = callback } }