UNPKG

vue

Version:

Reactive, component-oriented view layer for modern web interfaces.

323 lines (292 loc) 8.73 kB
/* @flow */ import VNode from './vnode' import { resolveConstructorOptions } from '../instance/init' import { activeInstance, callHook } from '../instance/lifecycle' import { resolveSlots } from '../instance/render' import { createElement } from './create-element' import { warn, isObject, hasOwn, hyphenate, validateProp } from '../util/index' const hooks = { init, prepatch, insert, destroy } const hooksToMerge = Object.keys(hooks) export function createComponent ( Ctor: Class<Component> | Function | Object | void, data?: VNodeData, context: Component, children: ?Array<VNode>, tag?: string ): VNode | void { if (!Ctor) { return } const baseCtor = context.$options._base if (isObject(Ctor)) { Ctor = baseCtor.extend(Ctor) } if (typeof Ctor !== 'function') { if (process.env.NODE_ENV !== 'production') { warn(`Invalid Component definition: ${String(Ctor)}`, context) } return } // async component if (!Ctor.cid) { if (Ctor.resolved) { Ctor = Ctor.resolved } else { Ctor = resolveAsyncComponent(Ctor, baseCtor, () => { // it's ok to queue this on every render because // $forceUpdate is buffered by the scheduler. context.$forceUpdate() }) if (!Ctor) { // return nothing if this is indeed an async component // wait for the callback to trigger parent update. return } } } // resolve constructor options in case global mixins are applied after // component constructor creation resolveConstructorOptions(Ctor) data = data || {} // extract props const propsData = extractProps(data, Ctor) // functional component if (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 data.on = data.nativeOn if (Ctor.options.abstract) { // abstract components do not keep anything // other than props & listeners data = {} } // merge component management hooks onto the placeholder node mergeHooks(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 } ) return vnode } function createFunctionalComponent ( Ctor: Class<Component>, propsData: ?Object, data: VNodeData, context: Component, children: ?Array<VNode> ): VNode | void { const props = {} const propOptions = Ctor.options.props if (propOptions) { for (const key in propOptions) { props[key] = validateProp(key, propOptions, propsData) } } // ensure the createElement function in functional components // gets a unique context - this is necessary for correct named slot check const _context = Object.create(context) const h = (a, b, c, d) => createElement(_context, a, b, c, d, true) const vnode = Ctor.options.render.call(null, h, { props, data, parent: context, children, slots: () => resolveSlots(children, context) }) if (vnode instanceof VNode) { vnode.functionalContext = context if (data.slot) { (vnode.data || (vnode.data = {})).slot = data.slot } } return vnode } export function createComponentInstanceForVnode ( vnode: any, // we know it's MountedComponentVNode but flow doesn't parent: any, // activeInstance in lifecycle state parentElm?: ?Node, refElm?: ?Node ): Component { const vnodeComponentOptions = vnode.componentOptions const options: InternalComponentOptions = { _isComponent: true, parent, propsData: vnodeComponentOptions.propsData, _componentTag: vnodeComponentOptions.tag, _parentVnode: vnode, _parentListeners: vnodeComponentOptions.listeners, _renderChildren: vnodeComponentOptions.children, _parentElm: parentElm || null, _refElm: refElm || null } // check inline-template render functions const inlineTemplate = vnode.data.inlineTemplate if (inlineTemplate) { options.render = inlineTemplate.render options.staticRenderFns = inlineTemplate.staticRenderFns } return new vnodeComponentOptions.Ctor(options) } function init ( vnode: VNodeWithData, hydrating: boolean, parentElm: ?Node, refElm: ?Node ): ?boolean { if (!vnode.componentInstance || vnode.componentInstance._isDestroyed) { const child = vnode.componentInstance = createComponentInstanceForVnode( vnode, activeInstance, parentElm, refElm ) child.$mount(hydrating ? vnode.elm : undefined, hydrating) } else if (vnode.data.keepAlive) { // kept-alive components, treat as a patch const mountedNode: any = vnode // work around flow prepatch(mountedNode, mountedNode) } } function prepatch ( oldVnode: MountedComponentVNode, vnode: MountedComponentVNode ) { const options = vnode.componentOptions const child = vnode.componentInstance = oldVnode.componentInstance child._updateFromParent( options.propsData, // updated props options.listeners, // updated listeners vnode, // new parent vnode options.children // new children ) } function insert (vnode: MountedComponentVNode) { if (!vnode.componentInstance._isMounted) { vnode.componentInstance._isMounted = true callHook(vnode.componentInstance, 'mounted') } if (vnode.data.keepAlive) { vnode.componentInstance._inactive = false callHook(vnode.componentInstance, 'activated') } } function destroy (vnode: MountedComponentVNode) { if (!vnode.componentInstance._isDestroyed) { if (!vnode.data.keepAlive) { vnode.componentInstance.$destroy() } else { vnode.componentInstance._inactive = true callHook(vnode.componentInstance, 'deactivated') } } } function resolveAsyncComponent ( factory: Function, baseCtor: Class<Component>, cb: Function ): Class<Component> | void { if (factory.requested) { // pool callbacks factory.pendingCallbacks.push(cb) } else { factory.requested = true const cbs = factory.pendingCallbacks = [cb] let sync = true const resolve = (res: Object | Class<Component>) => { if (isObject(res)) { res = baseCtor.extend(res) } // cache resolved factory.resolved = res // invoke callbacks only if this is not a synchronous resolve // (async resolves are shimmed as synchronous during SSR) if (!sync) { for (let i = 0, l = cbs.length; i < l; i++) { cbs[i](res) } } } const reject = reason => { process.env.NODE_ENV !== 'production' && warn( `Failed to resolve async component: ${String(factory)}` + (reason ? `\nReason: ${reason}` : '') ) } const res = factory(resolve, reject) // handle promise if (res && typeof res.then === 'function' && !factory.resolved) { res.then(resolve, reject) } sync = false // return in case resolved synchronously return factory.resolved } } function extractProps (data: VNodeData, Ctor: Class<Component>): ?Object { // we are only extracting raw values here. // validation and default values are handled in the child // component itself. const propOptions = Ctor.options.props if (!propOptions) { return } const res = {} const { attrs, props, domProps } = data if (attrs || props || domProps) { for (const key in propOptions) { const altKey = hyphenate(key) checkProp(res, props, key, altKey, true) || checkProp(res, attrs, key, altKey) || checkProp(res, domProps, key, altKey) } } return res } function checkProp ( res: Object, hash: ?Object, key: string, altKey: string, preserve?: boolean ): boolean { if (hash) { 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 } function mergeHooks (data: VNodeData) { if (!data.hook) { data.hook = {} } for (let i = 0; i < hooksToMerge.length; i++) { const key = hooksToMerge[i] const fromParent = data.hook[key] const ours = hooks[key] data.hook[key] = fromParent ? mergeHook(ours, fromParent) : ours } } function mergeHook (one: Function, two: Function): Function { return function (a, b, c, d) { one(a, b, c, d) two(a, b, c, d) } }