| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268 |
- /* @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 (
- vnode: any, // we know it's MountedComponentVNode but flow doesn't
- parent: any, // activeInstance in lifecycle state
- ): 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
- }
- }
|