vue
Version:
Reactive, component-oriented view layer for modern web interfaces.
156 lines (141 loc) • 4.57 kB
JavaScript
/* @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<VNode>,
parent: Component,
Ctor: Class<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)
// $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<Component>,
propsData: ?Object,
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 (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]
}
}