| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325 |
- /* @flow */
- import VNode from './vnode'
- import { normalizeChildren } from './helpers/index'
- 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, bind } 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?: VNodeChildren,
- 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, undefined, context,
- { Ctor, propsData, listeners, tag, children }
- )
- return vnode
- }
- function createFunctionalComponent (
- Ctor: Class<Component>,
- propsData: ?Object,
- data: VNodeData,
- context: Component,
- children?: VNodeChildren
- ): VNode | void {
- const props = {}
- const propOptions = Ctor.options.props
- if (propOptions) {
- for (const key in propOptions) {
- props[key] = validateProp(key, propOptions, propsData)
- }
- }
- const vnode = Ctor.options.render.call(
- null,
- // ensure the createElement function in functional components
- // gets a unique context - this is necessary for correct named slot check
- bind(createElement, { _self: Object.create(context) }),
- {
- props,
- data,
- parent: context,
- children: normalizeChildren(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.child || vnode.child._isDestroyed) {
- const child = vnode.child = 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.child = oldVnode.child
- child._updateFromParent(
- options.propsData, // updated props
- options.listeners, // updated listeners
- vnode, // new parent vnode
- options.children // new children
- )
- }
- function insert (vnode: MountedComponentVNode) {
- if (!vnode.child._isMounted) {
- vnode.child._isMounted = true
- callHook(vnode.child, 'mounted')
- }
- if (vnode.data.keepAlive) {
- vnode.child._inactive = false
- callHook(vnode.child, 'activated')
- }
- }
- function destroy (vnode: MountedComponentVNode) {
- if (!vnode.child._isDestroyed) {
- if (!vnode.data.keepAlive) {
- vnode.child.$destroy()
- } else {
- vnode.child._inactive = true
- callHook(vnode.child, '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)
- }
- }
|