/* @flow */
|
|
import VNode from './vnode'
|
import { resolveConstructorOptions } from 'core/instance/init'
|
import { queueActivatedComponent } from 'core/observer/scheduler'
|
import { createFunctionalComponent } from './create-functional-component'
|
|
import {
|
warn,
|
isDef,
|
isUndef,
|
isTrue,
|
isObject
|
} from '../util/index'
|
|
import {
|
resolveAsyncComponent,
|
createAsyncPlaceholder,
|
extractPropsFromVNodeData
|
} from './helpers/index'
|
|
import {
|
callHook,
|
activeInstance,
|
updateChildComponent,
|
activateChildComponent,
|
deactivateChildComponent
|
} from '../instance/lifecycle'
|
|
import {
|
isRecyclableComponent,
|
renderRecyclableComponentTemplate
|
} from 'weex/runtime/recycle-list/render-component-template'
|
|
// inline hooks to be invoked on component VNodes during patch
|
const componentVNodeHooks = {
|
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
|
if (
|
vnode.componentInstance &&
|
!vnode.componentInstance._isDestroyed &&
|
vnode.data.keepAlive
|
) {
|
// kept-alive components, treat as a patch
|
const mountedNode: any = vnode // work around flow
|
componentVNodeHooks.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.componentOptions
|
const child = vnode.componentInstance = oldVnode.componentInstance
|
updateChildComponent(
|
child,
|
options.propsData, // updated props
|
options.listeners, // updated listeners
|
vnode, // new parent vnode
|
options.children // new children
|
)
|
},
|
|
insert (vnode: MountedComponentVNode) {
|
const { context, componentInstance } = vnode
|
if (!componentInstance._isMounted) {
|
componentInstance._isMounted = true
|
callHook(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 } = vnode
|
if (!componentInstance._isDestroyed) {
|
if (!vnode.data.keepAlive) {
|
componentInstance.$destroy()
|
} else {
|
deactivateChildComponent(componentInstance, true /* direct */)
|
}
|
}
|
}
|
}
|
|
const hooksToMerge = Object.keys(componentVNodeHooks)
|
|
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 constructor
|
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)) {
|
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.
|
return createAsyncPlaceholder(
|
asyncFactory,
|
data,
|
context,
|
children,
|
tag
|
)
|
}
|
}
|
|
data = data || {}
|
|
// resolve constructor options in case global mixins are applied after
|
// component constructor creation
|
resolveConstructorOptions(Ctor)
|
|
// transform component v-model data into props & events
|
if (isDef(data.model)) {
|
transformModel(Ctor.options, data)
|
}
|
|
// extract props
|
const propsData = extractPropsFromVNodeData(data, Ctor, tag)
|
|
// functional component
|
if (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 listeners
|
const listeners = data.on
|
// replace with listeners with .native modifier
|
// so it gets processed during parent component patch.
|
data.on = data.nativeOn
|
|
if (isTrue(Ctor.options.abstract)) {
|
// abstract components do not keep anything
|
// other than props & listeners & slot
|
|
// work around flow
|
const slot = data.slot
|
data = {}
|
if (slot) {
|
data.slot = slot
|
}
|
}
|
|
// install component management hooks onto the placeholder node
|
installComponentHooks(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 },
|
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
|
}
|
|
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
|
}
|
// check inline-template render functions
|
const inlineTemplate = vnode.data.inlineTemplate
|
if (isDef(inlineTemplate)) {
|
options.render = inlineTemplate.render
|
options.staticRenderFns = inlineTemplate.staticRenderFns
|
}
|
return new vnode.componentOptions.Ctor(options)
|
}
|
|
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]
|
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
|
}
|
|
// transform component v-model info (value and callback) into
|
// prop and event handler respectively.
|
function transformModel (options, data: any) {
|
const prop = (options.model && options.model.prop) || 'value'
|
const event = (options.model && options.model.event) || 'input'
|
;(data.attrs || (data.attrs = {}))[prop] = data.model.value
|
const on = data.on || (data.on = {})
|
const existing = on[event]
|
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
|
}
|
}
|