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<VNode> | 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>
|
): VNode | Array<VNode> | 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]
|
}
|
}
|