Vue 2.0 相比 Vue 1.0 最大的降级就是利用了虚构DOM。 在 Vue 1.0 中视图的更新是纯响应式的。在进行响应式初始化的时候,一个响应式数据 key
会创立一个对应的 dep
,这个 key
在模板中被援用几次就会创立几个 watcher
。也就是一个 key
对应一个 dep
,dep
内治理一个或者多个 watcher
。因为 watcher
和 DOM
是一对一的关系,更新时,能够明确的对某个 DOM
进行更新,更新效率还是很高的。
随着利用越来越大,组件越来越多,一个组件就可能存在大量的 watcher
,性能就成了问题。Vue 2.0 退出了虚构DOM和 diff 后,一个组件就只须要创立一个 watcher
了,更新形式应用响应式+diff,组件之间应用响应式,组件外部应用 diff 。当数据发生变化时,告诉 watcher
更新,也就是告诉整个组件更新,具体更新什么元素什么地位,就能够通过 diff 算法比照新旧虚构DOM获取差别,把差别真正的更新到视图。
虚构DOM在 React、Vue 都有使用,一方面能够晋升一些性能,另一方面也能够更好的跨平台,比方服务端渲染。
虚构DOM,也就是VNode,实质上是应用 js 对象来模仿实在DOM中存在的节点。举个栗子:
<div id="app"> <h1>虚构DOM<h1></div>
{ tag: "div", attrs: { id: "app" }, children: [ { tag: "h1", text: "虚构DOM" } ]}
VNode
Vue 中的虚构DOM是用 一个 Class 去形容的。咱们先来看看:
// src/core/vdom/vnode.jsexport default class VNode { tag: string | void; data: VNodeData | void; children: ?Array<VNode>; text: string | void; elm: Node | void; ns: string | void; context: Component | void; // rendered in this component's scope key: string | number | void; componentOptions: VNodeComponentOptions | void; componentInstance: Component | void; // component instance parent: VNode | void; // component placeholder node // strictly internal raw: boolean; // contains raw HTML? (server only) isStatic: boolean; // hoisted static node isRootInsert: boolean; // necessary for enter transition check isComment: boolean; // empty comment placeholder? isCloned: boolean; // is a cloned node? isOnce: boolean; // is a v-once node? asyncFactory: Function | void; // async component factory function asyncMeta: Object | void; isAsyncPlaceholder: boolean; ssrContext: Object | void; fnContext: Component | void; // real context vm for functional nodes fnOptions: ?ComponentOptions; // for SSR caching devtoolsMeta: ?Object; // used to store functional render context for devtools fnScopeId: ?string; // functional scope id support constructor ( tag?: string, data?: VNodeData, children?: ?Array<VNode>, text?: string, elm?: Node, context?: Component, componentOptions?: VNodeComponentOptions, asyncFactory?: Function ) { this.tag = tag this.data = data this.children = children this.text = text this.elm = elm this.ns = undefined this.context = context this.fnContext = undefined this.fnOptions = undefined this.fnScopeId = undefined this.key = data && data.key this.componentOptions = componentOptions this.componentInstance = undefined this.parent = undefined this.raw = false this.isStatic = false this.isRootInsert = true this.isComment = false this.isCloned = false this.isOnce = false this.asyncFactory = asyncFactory this.asyncMeta = undefined this.isAsyncPlaceholder = false } // DEPRECATED: alias for componentInstance for backwards compat. /* istanbul ignore next */ get child (): Component | void { return this.componentInstance }}
_render
在之前介绍过,响应式数据发生变化时,触发更新其实就是执行上面代码:
updateComponent = () => { vm._update(vm._render(), hydrating)}
先执行 vm._render()
,获取VNode ,做为参数传递给 vm._update
去更新视图。咱们先看看 vm._render()
的定义:
// src/core/instance/render.jsexport function renderMixin (Vue: Class<Component>) { // install runtime convenience helpers // 在组件实例上挂载一些运行时须要用到的工具办法 installRenderHelpers(Vue.prototype) Vue.prototype.$nextTick = function (fn: Function) { return nextTick(fn, this) } /** * 通过执行 render 函数生成 VNode * 不过外面加了大量的异样解决代码 */ Vue.prototype._render = function (): VNode { const vm: Component = this const { render, _parentVnode } = vm.$options if (_parentVnode) { vm.$scopedSlots = normalizeScopedSlots( _parentVnode.data.scopedSlots, vm.$slots, vm.$scopedSlots ) } // set parent vnode. this allows render functions to have access // to the data on the placeholder node. // 设置父 vnode。这使得渲染函数能够拜访占位符节点上的数据。 vm.$vnode = _parentVnode // render self let vnode try { // There's no need to maintain a stack because all render fns are called // separately from one another. Nested component's render fns are called // when parent component is patched. currentRenderingInstance = vm // 执行 render 函数,生成 vnode vnode = render.call(vm._renderProxy, vm.$createElement) } catch (e) { handleError(e, vm, `render`) // 错误处理,开发环境渲染错误信息,生产环境返回之前的 vnode,以避免渲染谬误导致组件空白 if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) { try { vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e) } catch (e) { handleError(e, vm, `renderError`) vnode = vm._vnode } } else { vnode = vm._vnode } } finally { currentRenderingInstance = null } // 如果 vnode 是数组且只有一项,间接放回该项 if (Array.isArray(vnode) && vnode.length === 1) { vnode = vnode[0] } // render 函数出错时,返回一个空的 vnode if (!(vnode instanceof VNode)) { if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) { warn( 'Multiple root nodes returned from render function. Render function ' + 'should return a single root node.', vm ) } vnode = createEmptyVNode() } // set parent vnode.parent = _parentVnode return vnode }}
_render
中会调用 render
办法,这里有两种状况:
模板编译而来的 render
with(this){return _c(tag, data, children, normalizationType)}
用户定义的 render
render: function (createElement) {return createElement('div', { attrs: { id: 'app' },}, this.msg)}
下面能够看出模板编译进去的
render
理论就是调用_c
办法,用户自定义的render
理论就是vm.$createElement
办法。// src/core/instance/render.jsexport function initRender (vm: Component) {/** * @param {*} a 标签名 * @param {*} b 属性的 JSON 字符串 * @param {*} c 子节点数组 * @param {*} d 节点的规范化类型 * @returns VNode or Array<VNode> */ vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)}
这两个办法反对雷同的参数,理论调用的都是
createElement
办法。
createElement
// src/core/vdom/create-element.js// 生成组件或一般标签的 vnode,一个包装器函数,用于提供更灵便的接口。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.jsexport 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__)) { // 如果 data 是一个响应式对象,返回空节点的Vnode 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-bind if (isDef(data) && isDef(data.is)) { tag = data.is } if (!tag) { // 动静组件:is 属性被设置为 false 时 // in case of component :is set to falsy value return createEmptyVNode() } // 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') { let Ctor ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag) if (config.isReservedTag(tag)) { // 平台内置的节点 // platform built-in elements if (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))) { // tag 是自定义组件 // component vnode = 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 children // 未知的标签 tag ,在运行时查看,因为当其父项规范化子项时,可能会为其调配命名空间 vnode = new VNode( tag, data, children, undefined, undefined, context ) } } else { // tag 非字符串时 // direct component options / constructor vnode = 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() }}
_createElement
办法内一开先是对一些非凡状况的判断,咱们只关注主流程,也就是生成 VNode 这一块,这里对 tag
先是判断如果是 string
类型,有三种状况:
- 如果是一些平台内置节点,间接创立一个一般的 VNode.
- 如果为已注册的组件名,通过
createComponent
创立一个组件类型的 VNode. - 否则创立一个未知标签名的 VNode.
这里还有一个值得注意的中央就是通过 normalizeChildren
和 simpleNormalizeChildren
办法对子节点进行规范化,使其每个节点都是 VNode 类型。
simpleNormalizeChildren
办法调用场景是 render
函数是编译生成的。实践上编译生成的 children
都曾经是 VNode 类型的,但这里有一个例外,就是 functional component
函数式组件返回的是一个数组而不是一个根节点,所以会通过 Array.prototype.concat
办法把整个 children
数组打平,让它的深度只有一层。
normalizeChildren
办法的调用场景有 2 种,一个场景是 render
函数是用户手写的,当 children
只有一个节点的时候,Vue.js 从接口层面容许用户把 children
写成根底类型用来创立单个简略的文本节点,这种状况会调用 createTextVNode
创立一个文本节点的 VNode;另一个场景是当编译 slot、v-for 的时候会产生嵌套数组的状况,会调用 normalizeArrayChildren
办法
组件类型
createComponent
// src/core/vdom/create-component.jsexport 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 } // context.$options._base = Vue.options._base = Vue const baseCtor = context.$options._base // Vue.extend 定义在 src/core/global-api/extend.js // 当 Ctor 为配置对象时,通过 Vue.extend 结构一个 Vue 的子类 // 当 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. 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)) { // 如果Ctor.cid为空,那么Ctor就是一个函数,表明这是一个异步组件 asyncFactory = Ctor Ctor = 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. // 为异步组件返回一个占位符节点,组件被渲染为正文节点,但保留了节点的所有原始信息,这些信息将用于异步服务器渲染 和 hydration return createAsyncPlaceholder( asyncFactory, data, context, children, tag ) } } // 节点的属性 JSON 字符串 data = data || {} // 合并选项,在组件构造函数创立后利用全局混合的状况下解析构造函数选项 // resolve constructor options in case global mixins are applied after // component constructor creation resolveConstructorOptions(Ctor) // 将组件的 v-model 转换为 data.attrs 对象的属性、值和 data.on 对象上的事件、回调 // transform component v-model data into props & events if (isDef(data.model)) { transformModel(Ctor.options, data) } // 提取 props 数据,失去 propsData 对象,propsData[key] = val // extract props const propsData = extractPropsFromVNodeData(data, Ctor, tag) // functional component (函数式组件) if (isTrue(Ctor.options.functional)) { return createFunctionalComponent(Ctor, propsData, data, context, children) } // 提取事件侦听,因为这些侦听器须要被视为子组件侦听器,而不是DOM侦听器 // extract listeners, since these needs to be treated as // child component listeners instead of DOM listeners const listeners = data.on // 替换为带有.native修饰符的侦听器,以便在父组件修补程序期间对其进行解决。 // replace with listeners with .native modifier // so it gets processed during parent component patch. 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 } } // 装置组件钩子函数 init、prepatch、insert、destroy // install component management hooks onto the placeholder node installComponentHooks(data) // 通过 new VNode 实例化一个 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}
从下面代码能够看出,对异步组件、函数式组件和一般组件都别离做了解决。就一般组件而言,createComponent
办法大抵做了这么几件事:
- 通过
Vue.extend
构建子类的构造函数。 - 装置组件钩子函数
init
、prepatch
、insert
、destroy
。 - 实例化 VNode,返回 VNode。
createComponent
内的逻辑有点简单,咱们依照几种组件的类别,先来看一般组件:
resolveConstructorOptions
// src/core/instance/init.js/** * @description: 从组件构造函数中解析配置对象 options,并合并基类选项 * @param {*} Ctor * @return {Object} options */export function resolveConstructorOptions (Ctor: Class<Component>) { // 从实例构造函数上获取选项 let options = Ctor.options if (Ctor.super) { // 存在基类,递归解析基类构造函数的选项 const superOptions = resolveConstructorOptions(Ctor.super) // 缓存 const cachedSuperOptions = Ctor.superOptions if (superOptions !== cachedSuperOptions) { // 阐明基类的配置项产生了更改 Ctor.superOptions = superOptions // 找到更改的选项 const modifiedOptions = resolveModifiedOptions(Ctor) // 如果存在被批改或减少的选项,则合并两个选项 if (modifiedOptions) { // 将更改的选项和 extend 选项合并 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/** * @description: 解析构造函数选项中后续被批改或者减少的选项 * @param {*} Ctor * @return {*} modified 不统一的选项 */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 }}
extractPropsFromVNodeData
// src/core/vdom/helpers/extract-props.jsexport function extractPropsFromVNodeData ( data: VNodeData, Ctor: Class<Component>, tag?: string): ?Object { // 只提取原始值,验证和默认值在子组件中解决 // we are only extracting raw values here. // validation and default values are handled in the child // component itself. const propOptions = Ctor.options.props // 未定义 props if (isUndef(propOptions)) { return } const res = {} const { attrs, props } = data if (isDef(attrs) || isDef(props)) { // 遍历 propsOptions for (const key in propOptions) { // 将小驼峰模式的 key 转换为 xxx-xxx-xxx 模式 const altKey = hyphenate(key) if (process.env.NODE_ENV !== 'production') { // 谬误提醒 const keyInLowerCase = key.toLowerCase() if ( key !== keyInLowerCase && attrs && hasOwn(attrs, keyInLowerCase) ) { tip( `Prop "${keyInLowerCase}" is passed to component ` + `${formatComponentName(tag || Ctor)}, but the declared prop name is` + ` "${key}". ` + `Note that HTML attributes are case-insensitive and camelCased ` + `props need to use their kebab-case equivalents when using in-DOM ` + `templates. You should probably use "${altKey}" instead of "${key}".` ) } } checkProp(res, props, key, altKey, true) || checkProp(res, attrs, key, altKey, false) } } return res}
checkProp
// src/core/vdom/helpers/extract-props.jsfunction checkProp ( res: Object, hash: ?Object, key: string, altKey: string, preserve: boolean): boolean { if (isDef(hash)) { // 判断 hash(props/attrs)对象中是否存在 key 或 altKey // 存在则设置给 res => res[key] = hash[key] if (hasOwn(hash, key)) { res[key] = hash[key] if (!preserve) { delete hash[key] } return true } else if (hasOwn(hash, altKey)) { res[key] = hash[altKey] if (!preserve) { delete hash[altKey] } return true } } return false}
installComponentHooks
installComponentHooks
的作用就是把 componentVNodeHooks
的钩子函数合并到 data.hook
中,在合并过程,如果某个钩子存在,执行 mergeHook
函数做合并。在 VNode 执行 patch
的过程中会执行相干的钩子函数。
// src/core/vdom/create-component.js/** * 在组件的 data 对象上设置 hook 对象, * hook 对象减少四个属性,init、prepatch、insert、destroy, * 负责组件的创立、更新、销毁 */function installComponentHooks (data: VNodeData) { const hooks = data.hook || (data.hook = {}) // 遍历 hooksToMerge 数组,hooksToMerge = ['init', 'prepatch', 'insert' 'destroy'] for (let i = 0; i < hooksToMerge.length; i++) { // 比方 key = init const key = hooksToMerge[i] // 从 data.hook 对象中获取 key 对应的办法 const existing = hooks[key] // componentVNodeHooks 对象中 key 对象的办法 const toMerge = componentVNodeHooks[key] // 合并用户传递的 hook 办法和框架自带的 hook 办法,其实就是别离执行两个办法 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 any f1(a, b) f2(a, b) } merged._merged = true return merged}
componentVNodeHooks
// src/core/vdom/create-component.js// patch 期间在组件 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 ) { // 被 keep-alive 包裹的组件 // kept-alive components, treat as a patch const mountedNode: any = vnode // work around flow componentVNodeHooks.prepatch(mountedNode, mountedNode) } else { // 创立组件实例,即 new vnode.componentOptions.Ctor(options) => 失去 Vue 组件实例 const child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance ) // 调用 $mount 办法,进入挂载阶段。 child.$mount(hydrating ? vnode.elm : undefined, hydrating) } }, // 更新 VNode prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) { // 新的 VNode 配置项 const options = vnode.componentOptions // 旧的 VNode 组件实例 const child = vnode.componentInstance = oldVnode.componentInstance // 用新的更新旧的 updateChildComponent( child, options.propsData, // updated props options.listeners, // updated listeners vnode, // new parent vnode options.children // new children ) }, // 执行组件的 mounted 钩子函数 insert (vnode: MountedComponentVNode) { const { context, componentInstance } = vnode if (!componentInstance._isMounted) { componentInstance._isMounted = true callHook(componentInstance, 'mounted') } // 解决 keep-alive 组件的异常情况 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) { // 间接调用 $destroy 销毁组件 componentInstance.$destroy() } else { // 被 keep-alive 包裹的组件 // 让组件失活,不销毁组件实例,从而缓存组件的状态 deactivateChildComponent(componentInstance, true /* direct */) } } }}
createComponentInstanceForVnode
// src/core/vdom/create-component.js// new vnode.componentOptions.Ctor(options) => 失去 Vue 组件实例 export function createComponentInstanceForVnode ( // we know it's MountedComponentVNode but flow doesn't vnode: any, // activeInstance in lifecycle state parent: any): Component { const options: InternalComponentOptions = { _isComponent: true, _parentVnode: vnode, parent } // 查看内联模版渲染函数 const inlineTemplate = vnode.data.inlineTemplate if (isDef(inlineTemplate)) { options.render = inlineTemplate.render options.staticRenderFns = inlineTemplate.staticRenderFns } // new VueComponent(options) => Vue 实例 return new vnode.componentOptions.Ctor(options)}
最初就是实例化 VNode,返回 VNode。除了一般组件,还有分支:异步组件和函数组件。上面看看异步组件和函数组件的流程:
异步组件
// src/core/vdom/create-component.js —— createComponent 办法中// async component (异步组件)let asyncFactoryif (isUndef(Ctor.cid)) { asyncFactory = Ctor Ctor = 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. // 为异步组件返回一个占位符节点,组件被渲染为正文节点,但保留了节点的所有原始信息,这些信息将用于异步服务器渲染 和 hydration return createAsyncPlaceholder( asyncFactory, data, context, children, tag ) }}
resolveAsyncComponent
// src/core/vdom/helpers/resolve-async-component.jsexport function resolveAsyncComponent ( factory: Function, baseCtor: Class<Component>): Class<Component> | void { if (isTrue(factory.error) && isDef(factory.errorComp)) { return factory.errorComp } if (isDef(factory.resolved)) { return factory.resolved } // owner 实例收集容器,对同一个异步组件的援用不用屡次解析,而是将以后应用该异步组件的实例收集起来,待到异步组件解析结束,挨个告诉渲染更新即可 const owner = currentRenderingInstance if (owner && isDef(factory.owners) && factory.owners.indexOf(owner) === -1) { // already pending factory.owners.push(owner) } if (isTrue(factory.loading) && isDef(factory.loadingComp)) { return factory.loadingComp } //... // 这块代码有点长,咱们在上面把它分为:一般异步组件、Promise异步组件,高级异步组件,离开来看}
针对一般函数的状况,后面几个 if 判断能够疏忽,它们是为高级组件所用,对于 factory.xxxxx
的判断,是思考到多个中央同时初始化一个异步组件,那么它的理论加载应该只有一次。
createAsyncPlaceholder
// src/core/vdom/helpers/resolve-async-component.js// 为异步组件返回一个占位符节点,组件被渲染为正文节点,但保留了节点的所有原始信息export function createAsyncPlaceholder ( factory: Function, data: ?VNodeData, context: Component, children: ?Array<VNode>, tag: ?string): VNode { const node = createEmptyVNode() node.asyncFactory = factory node.asyncMeta = { data, context, children, tag } return node}
一般异步组件
Vue.component('async-example', function (resolve, reject) { // 这个非凡的 require 语法通知 webpack // 主动将编译后的代码宰割成不同的块, // 这些块将通过 Ajax 申请主动下载。 require(['./my-async-component'], resolve) } )
Vue 容许将组件定义为一个工厂函数,动静的解析组件,Vue 只在组件须要渲染时触发工厂函数,并且把后果缓存起来,用于前面的再次渲染。
// src/core/vdom/helpers/resolve-async-component.jsexport function resolveAsyncComponent ( factory: Function, baseCtor: Class<Component>): Class<Component> | void { //一般异步组件第二次执行这里时会返回factory.resolved if (isDef(factory.resolved)) { return factory.resolved } // owner 实例收集容器,对同一个异步组件的援用不用屡次解析,而是将以后应用该异步组件的实例收集起来,待到异步组件解析结束,挨个告诉渲染更新即可 const owner = currentRenderingInstance if (owner && isDef(factory.owners) && factory.owners.indexOf(owner) === -1) { // already pending factory.owners.push(owner) } if (owner && !isDef(factory.owners)) { // owner 实例收集容器 const owners = factory.owners = [owner] // sync 同步标识符,标识以后是同步还是异步 let sync = true let timerLoading = null let timerTimeout = null ; (owner: any).$on('hook:destroyed', () => remove(owners, owner)) const forceRender = (renderCompleted: boolean) => { // 顺次调用该元素的 $forceUpdate()办法 该办法会强制渲染一次 for (let i = 0, l = owners.length; i < l; i++) { (owners[i]: any).$forceUpdate() } // 组件更新之后 移除定时器,清空依赖 if (renderCompleted) { owners.length = 0 if (timerLoading !== null) { clearTimeout(timerLoading) timerLoading = null } if (timerTimeout !== null) { clearTimeout(timerTimeout) timerTimeout = null } } } // 定义一个 resolve 函数, once是一次性包装函数,保障传入的函数只执行一次(防止屡次执行告诉更新操作) const resolve = once((res: Object | Class<Component>) => { // 缓存 resolved factory.resolved = ensureCtor(res, baseCtor) // invoke callbacks only if this is not a synchronous resolve // (async resolves are shimmed as synchronous during SSR) if (!sync) { forceRender(true) } else { owners.length = 0 } }) //定义一个 reject 函数 const reject = once(reason => { process.env.NODE_ENV !== 'production' && warn( `Failed to resolve async component: ${String(factory)}` + (reason ? `\nReason: ${reason}` : '') ) if (isDef(factory.errorComp)) { factory.error = true forceRender(true) } }) //执行 factory()函数 const res = factory(resolve, reject) sync = false // return in case resolved synchronously return factory.loading ? factory.loadingComp : factory.resolved }}
resolveAsyncComponent
外部会定义一个 resolve
和 reject
函数,而后执行 factory()
函数,factory()
就是咱们在组件定义的函数,函数内会执行 require
函数,因为 require()
是个异步操作,所以 resolveAsyncComponent
就会返回 undefined
。
回到 createComponent
函数,因为返回的是 undefined
,则会执行 createAsyncPlaceholder
去创立一个正文节点占位符。
在下一个 tick 等 require
加载胜利后就会执行 resolve
函数,也就是在 resolveAsyncComponent
内定义的 resolve
函数,resolve
函数会将后果保留到工厂函数的 resolved
属性里。
再次从新渲染执行到 resolveAsyncComponent
的时候 factory.resolved
存在了,就间接返回。
Promise异步组件
Vue.component( 'async-webpack-example', // 该 `import` 函数返回一个 `Promise` 对象。 () => import('./my-async-component'))
webpack 2+ 反对了异步加载的语法糖:() => import('./my-async-component')
,当执行完 res = factory(resolve, reject)
,返回的值就是 import('./my-async-component')
的返回值,它是一个 Promise
对象。
// src/core/vdom/helpers/resolve-async-component.jsexport function resolveAsyncComponent ( factory: Function, baseCtor: Class<Component>): Class<Component> | void { // 组件第二次执行这里时会返回factory.resolved if (isDef(factory.resolved)) { return factory.resolved } if (owner && !isDef(factory.owners)) { // ... // 执行 factory()函数, // 返回一个含有then的对象 const res = factory(resolve, reject) if (isObject(res)) { if (isPromise(res)) { // () => Promise if (isUndef(factory.resolved)) { // 如果 factory.resolved 不存在 // 用 then 办法指定 resolve 和 reject 的回调函数 res.then(resolve, reject) } } else if (isPromise(res.component)) { // ... } } sync = false // return in case resolved synchronously return factory.loading ? factory.loadingComp : factory.resolved }}
当组件异步加载胜利后,执行 resolve
,加载失败则执行 reject
,很好的配合 webpack 2+ 的异步加载组件的形式(Promise)。
高级异步组件
高级异步组件能够定义更多的状态,比方加载该组件的超时工夫、加载过程中显式的组件、出错时显式的组件、延迟时间等。
const AsyncComp = () => ({ component: import('./MyComp.vue'), loading: LoadingComp, error: ErrorComp, delay: 200, timeout: 3000})Vue.component('async-example', AsyncComp)
对于高级异步组件来说,他和promise()办法加载的逻辑是一样的,不同的是多了几个属性:
// src/core/vdom/helpers/resolve-async-component.jsexport function resolveAsyncComponent ( factory: Function, baseCtor: Class<Component>): Class<Component> | void { //... if (owner && !isDef(factory.owners)) { // ... //执行 factory()函数 const res = factory(resolve, reject) if (isObject(res)) { if (isPromise(res)) { // () => Promise if (isUndef(factory.resolved)) { res.then(resolve, reject) } } else if (isPromise(res.component)) { // 高级异步组件的分支 res.component.then(resolve, reject) if (isDef(res.error)) { // 失败时的模块 factory.errorComp = ensureCtor(res.error, baseCtor) } if (isDef(res.loading)) { // 如果有设置加载时的模块 factory.loadingComp = ensureCtor(res.loading, baseCtor) if (res.delay === 0) { // 如果等待时间为0 factory.loading = true } else { timerLoading = setTimeout(() => { timerLoading = null if (isUndef(factory.resolved) && isUndef(factory.error)) { factory.loading = true forceRender(false) } }, res.delay || 200) } } if (isDef(res.timeout)) { // 超时工夫 timerTimeout = setTimeout(() => { timerTimeout = null if (isUndef(factory.resolved)) { reject( process.env.NODE_ENV !== 'production' ? `timeout (${res.timeout}ms)` : null ) } }, res.timeout) } } } sync = false // return in case resolved synchronously return factory.loading ? factory.loadingComp : factory.resolved }}
函数式组件
// src/core/vdom/create-component.js —— createComponent 办法中// functional component (函数式组件)if (isTrue(Ctor.options.functional)) { return createFunctionalComponent(Ctor, propsData, data, context, children) }
createFunctionalComponent
// src/core/vdom/create-functional-component.jsexport function createFunctionalComponent ( Ctor: Class<Component>, propsData: ?Object, data: VNodeData, contextVm: Component, children: ?Array<VNode>): VNode | Array<VNode> | void { //配置项 const options = Ctor.options // props const props = {} // 组件自身的 props const propOptions = options.props // 设置函数式组件的 props 对象 if (isDef(propOptions)) { // 函数式组件自身提供了 props 选项,则将 props.key 的值设置为组件上传递下来的对应 key 的值 for (const key in propOptions) { props[key] = validateProp(key, propOptions, propsData || emptyObject) } } else { // 函数式组件没有提供 props 选项,则将组件上的 attribute 主动解析为 props if (isDef(data.attrs)) mergeProps(props, data.attrs) if (isDef(data.props)) mergeProps(props, data.props) } // 实例化函数式组件的渲染上下文 const renderContext = new FunctionalRenderContext( data, props, children, contextVm, Ctor ) // 调用 render 函数,生成 vnode,并给 render 函数传递 _c 和 渲染上下文 const vnode = options.render.call(null, renderContext._c, renderContext) // 生成的 VNode 对象上加一些标记,示意该 VNode 是一个函数式组件生成的,最初返回 VNode if (vnode instanceof VNode) { return cloneAndMarkFunctionalResult(vnode, data, renderContext.parent, options, renderContext) } else if (Array.isArray(vnode)) { const vnodes = normalizeChildren(vnode) || [] const res = new Array(vnodes.length) for (let i = 0; i < vnodes.length; i++) { res[i] = cloneAndMarkFunctionalResult(vnodes[i], data, renderContext.parent, options, renderContext) } return res }}
createFunctionalComponent
在创立函数式组件的过程中,次要做了这么几件事:
- 设置组件的
props
对象 - 实例化组件,获取渲染上下文。
- 调用
render
办法,生成 VNode. - 返回标记后的 VNode。
归根到底,应用createElement
来创立元素的 VNode,createComponent
来创立组件的 VNode,每个 VNode 有children
,children
蕴含子 VNode ,就造成了一个 VNode Tree。_render
函数的执行就是为了取得到一颗 VNode 树。
相干链接
Vue源码解读(预):手写一个简易版Vue
Vue源码解读(一):筹备工作
Vue源码解读(二):初始化和挂载
Vue源码解读(三):响应式原理
Vue源码解读(四):更新策略
Vue源码解读(五):render和VNode
[Vue源码解读(六):update和patch(待续)]()
[Vue源码解读(七):模板编译(待续)]()
如果感觉还对付的话,给个赞吧!!!也能够来我的集体博客逛逛 https://www.mingme.net/