UNPKG

4.43 kBJavaScriptView Raw
1/* @flow */
2
3import VNode, { cloneVNode } from './vnode'
4import { createElement } from './create-element'
5import { resolveInject } from '../instance/inject'
6import { normalizeChildren } from '../vdom/helpers/normalize-children'
7import { resolveSlots } from '../instance/render-helpers/resolve-slots'
8import { normalizeScopedSlots } from '../vdom/helpers/normalize-scoped-slots'
9import { installRenderHelpers } from '../instance/render-helpers/index'
10
11import {
12 isDef,
13 isTrue,
14 hasOwn,
15 camelize,
16 emptyObject,
17 validateProp
18} from '../util/index'
19
20export function FunctionalRenderContext (
21 data: VNodeData,
22 props: Object,
23 children: ?Array<VNode>,
24 parent: Component,
25 Ctor: Class<Component>
26) {
27 const options = Ctor.options
28 // ensure the createElement function in functional components
29 // gets a unique context - this is necessary for correct named slot check
30 let contextVm
31 if (hasOwn(parent, '_uid')) {
32 contextVm = Object.create(parent)
33 // $flow-disable-line
34 contextVm._original = parent
35 } else {
36 // the context vm passed in is a functional context as well.
37 // in this case we want to make sure we are able to get a hold to the
38 // real context instance.
39 contextVm = parent
40 // $flow-disable-line
41 parent = parent._original
42 }
43 const isCompiled = isTrue(options._compiled)
44 const needNormalization = !isCompiled
45
46 this.data = data
47 this.props = props
48 this.children = children
49 this.parent = parent
50 this.listeners = data.on || emptyObject
51 this.injections = resolveInject(options.inject, parent)
52 this.slots = () => resolveSlots(children, parent)
53
54 Object.defineProperty(this, 'scopedSlots', ({
55 enumerable: true,
56 get () {
57 return normalizeScopedSlots(data.scopedSlots, this.slots())
58 }
59 }: any))
60
61 // support for compiled functional template
62 if (isCompiled) {
63 // exposing $options for renderStatic()
64 this.$options = options
65 // pre-resolve slots for renderSlot()
66 this.$slots = this.slots()
67 this.$scopedSlots = normalizeScopedSlots(data.scopedSlots, this.$slots)
68 }
69
70 if (options._scopeId) {
71 this._c = (a, b, c, d) => {
72 const vnode = createElement(contextVm, a, b, c, d, needNormalization)
73 if (vnode && !Array.isArray(vnode)) {
74 vnode.fnScopeId = options._scopeId
75 vnode.fnContext = parent
76 }
77 return vnode
78 }
79 } else {
80 this._c = (a, b, c, d) => createElement(contextVm, a, b, c, d, needNormalization)
81 }
82}
83
84installRenderHelpers(FunctionalRenderContext.prototype)
85
86export function createFunctionalComponent (
87 Ctor: Class<Component>,
88 propsData: ?Object,
89 data: VNodeData,
90 contextVm: Component,
91 children: ?Array<VNode>
92): VNode | Array<VNode> | void {
93 const options = Ctor.options
94 const props = {}
95 const propOptions = options.props
96 if (isDef(propOptions)) {
97 for (const key in propOptions) {
98 props[key] = validateProp(key, propOptions, propsData || emptyObject)
99 }
100 } else {
101 if (isDef(data.attrs)) mergeProps(props, data.attrs)
102 if (isDef(data.props)) mergeProps(props, data.props)
103 }
104
105 const renderContext = new FunctionalRenderContext(
106 data,
107 props,
108 children,
109 contextVm,
110 Ctor
111 )
112
113 const vnode = options.render.call(null, renderContext._c, renderContext)
114
115 if (vnode instanceof VNode) {
116 return cloneAndMarkFunctionalResult(vnode, data, renderContext.parent, options, renderContext)
117 } else if (Array.isArray(vnode)) {
118 const vnodes = normalizeChildren(vnode) || []
119 const res = new Array(vnodes.length)
120 for (let i = 0; i < vnodes.length; i++) {
121 res[i] = cloneAndMarkFunctionalResult(vnodes[i], data, renderContext.parent, options, renderContext)
122 }
123 return res
124 }
125}
126
127function cloneAndMarkFunctionalResult (vnode, data, contextVm, options, renderContext) {
128 // #7817 clone node before setting fnContext, otherwise if the node is reused
129 // (e.g. it was from a cached normal slot) the fnContext causes named slots
130 // that should not be matched to match.
131 const clone = cloneVNode(vnode)
132 clone.fnContext = contextVm
133 clone.fnOptions = options
134 if (process.env.NODE_ENV !== 'production') {
135 (clone.devtoolsMeta = clone.devtoolsMeta || {}).renderContext = renderContext
136 }
137 if (data.slot) {
138 (clone.data || (clone.data = {})).slot = data.slot
139 }
140 return clone
141}
142
143function mergeProps (to, from) {
144 for (const key in from) {
145 to[camelize(key)] = from[key]
146 }
147}