vue
Version:
Reactive, component-oriented view layer for modern web interfaces.
181 lines (167 loc) • 4.78 kB
text/typescript
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]
}
}