/* @flow */ import VNode, { cloneVNode } from './vnode' import { createElement } from './create-element' import { resolveInject } from '../instance/inject' import { normalizeChildren } from '../vdom/helpers/normalize-children' import { resolveSlots } from '../instance/render-helpers/resolve-slots' import { normalizeScopedSlots } from '../vdom/helpers/normalize-scoped-slots' import { installRenderHelpers } from '../instance/render-helpers/index' import { isDef, isTrue, hasOwn, camelize, emptyObject, validateProp } from '../util/index' export function FunctionalRenderContext ( data: VNodeData, props: Object, children: ?Array, parent: Component, Ctor: Class ) { const options = Ctor.options // ensure the createElement function in functional components // gets a unique context - this is necessary for correct named slot check let contextVm if (hasOwn(parent, '_uid')) { contextVm = Object.create(parent) // $flow-disable-line contextVm._original = parent } else { // the context vm passed in is a functional context as well. // in this case we want to make sure we are able to get a hold to the // real context instance. contextVm = parent // $flow-disable-line parent = parent._original } const isCompiled = isTrue(options._compiled) const needNormalization = !isCompiled this.data = data this.props = props this.children = children this.parent = parent this.listeners = data.on || emptyObject this.injections = resolveInject(options.inject, parent) this.slots = () => { if (!this.$slots) { normalizeScopedSlots( data.scopedSlots, this.$slots = resolveSlots(children, parent) ) } return this.$slots } Object.defineProperty(this, 'scopedSlots', ({ enumerable: true, get () { return normalizeScopedSlots(data.scopedSlots, this.slots()) } }: any)) // support for compiled functional template if (isCompiled) { // exposing $options for renderStatic() this.$options = options // pre-resolve slots for renderSlot() this.$slots = this.slots() this.$scopedSlots = normalizeScopedSlots(data.scopedSlots, this.$slots) } if (options._scopeId) { this._c = (a, b, c, d) => { const vnode = createElement(contextVm, a, b, c, d, needNormalization) if (vnode && !Array.isArray(vnode)) { vnode.fnScopeId = options._scopeId vnode.fnContext = parent } return vnode } } else { this._c = (a, b, c, d) => createElement(contextVm, a, b, c, d, needNormalization) } } installRenderHelpers(FunctionalRenderContext.prototype) export function createFunctionalComponent ( Ctor: Class, propsData: ?Object, data: VNodeData, contextVm: Component, children: ?Array ): VNode | Array | void { const options = Ctor.options const props = {} const propOptions = options.props if (isDef(propOptions)) { for (const key in propOptions) { props[key] = validateProp(key, propOptions, propsData || emptyObject) } } else { 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 ) const vnode = options.render.call(null, renderContext._c, renderContext) 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 } } function cloneAndMarkFunctionalResult (vnode, data, contextVm, options, renderContext) { // #7817 clone node before setting fnContext, otherwise if the node is reused // (e.g. it was from a cached normal slot) the fnContext causes named slots // that should not be matched to match. const clone = cloneVNode(vnode) clone.fnContext = contextVm clone.fnOptions = options if (process.env.NODE_ENV !== 'production') { (clone.devtoolsMeta = clone.devtoolsMeta || {}).renderContext = renderContext } if (data.slot) { (clone.data || (clone.data = {})).slot = data.slot } return clone } function mergeProps (to, from) { for (const key in from) { to[camelize(key)] = from[key] } }