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, isArray, camelize, emptyObject, validateProp } from '../util/index' import type { Component } from 'types/component' import type { VNodeData } from 'types/vnode' export function FunctionalRenderContext( data: VNodeData, props: Object, children: Array | undefined, parent: Component, Ctor: typeof Component ) { 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) 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 // @ts-ignore 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( parent, data.scopedSlots, (this.$slots = resolveSlots(children, parent)) ) } return this.$slots } Object.defineProperty(this, 'scopedSlots', { enumerable: true, get() { return normalizeScopedSlots(parent, data.scopedSlots, this.slots()) } } as 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( parent, data.scopedSlots, this.$slots ) } if (options._scopeId) { this._c = (a, b, c, d) => { const vnode = createElement(contextVm, a, b, c, d, needNormalization) if (vnode && !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: typeof Component, propsData: Object | undefined, 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 (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 (__DEV__) { ;(clone.devtoolsMeta = clone.devtoolsMeta || ({} as any)).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] } }