UNPKG

4.57 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 = () => {
53 if (!this.$slots) {
54 normalizeScopedSlots(
55 data.scopedSlots,
56 this.$slots = resolveSlots(children, parent)
57 )
58 }
59 return this.$slots
60 }
61
62 Object.defineProperty(this, 'scopedSlots', ({
63 enumerable: true,
64 get () {
65 return normalizeScopedSlots(data.scopedSlots, this.slots())
66 }
67 }: any))
68
69 // support for compiled functional template
70 if (isCompiled) {
71 // exposing $options for renderStatic()
72 this.$options = options
73 // pre-resolve slots for renderSlot()
74 this.$slots = this.slots()
75 this.$scopedSlots = normalizeScopedSlots(data.scopedSlots, this.$slots)
76 }
77
78 if (options._scopeId) {
79 this._c = (a, b, c, d) => {
80 const vnode = createElement(contextVm, a, b, c, d, needNormalization)
81 if (vnode && !Array.isArray(vnode)) {
82 vnode.fnScopeId = options._scopeId
83 vnode.fnContext = parent
84 }
85 return vnode
86 }
87 } else {
88 this._c = (a, b, c, d) => createElement(contextVm, a, b, c, d, needNormalization)
89 }
90}
91
92installRenderHelpers(FunctionalRenderContext.prototype)
93
94export function createFunctionalComponent (
95 Ctor: Class<Component>,
96 propsData: ?Object,
97 data: VNodeData,
98 contextVm: Component,
99 children: ?Array<VNode>
100): VNode | Array<VNode> | void {
101 const options = Ctor.options
102 const props = {}
103 const propOptions = options.props
104 if (isDef(propOptions)) {
105 for (const key in propOptions) {
106 props[key] = validateProp(key, propOptions, propsData || emptyObject)
107 }
108 } else {
109 if (isDef(data.attrs)) mergeProps(props, data.attrs)
110 if (isDef(data.props)) mergeProps(props, data.props)
111 }
112
113 const renderContext = new FunctionalRenderContext(
114 data,
115 props,
116 children,
117 contextVm,
118 Ctor
119 )
120
121 const vnode = options.render.call(null, renderContext._c, renderContext)
122
123 if (vnode instanceof VNode) {
124 return cloneAndMarkFunctionalResult(vnode, data, renderContext.parent, options, renderContext)
125 } else if (Array.isArray(vnode)) {
126 const vnodes = normalizeChildren(vnode) || []
127 const res = new Array(vnodes.length)
128 for (let i = 0; i < vnodes.length; i++) {
129 res[i] = cloneAndMarkFunctionalResult(vnodes[i], data, renderContext.parent, options, renderContext)
130 }
131 return res
132 }
133}
134
135function cloneAndMarkFunctionalResult (vnode, data, contextVm, options, renderContext) {
136 // #7817 clone node before setting fnContext, otherwise if the node is reused
137 // (e.g. it was from a cached normal slot) the fnContext causes named slots
138 // that should not be matched to match.
139 const clone = cloneVNode(vnode)
140 clone.fnContext = contextVm
141 clone.fnOptions = options
142 if (process.env.NODE_ENV !== 'production') {
143 (clone.devtoolsMeta = clone.devtoolsMeta || {}).renderContext = renderContext
144 }
145 if (data.slot) {
146 (clone.data || (clone.data = {})).slot = data.slot
147 }
148 return clone
149}
150
151function mergeProps (to, from) {
152 for (const key in from) {
153 to[camelize(key)] = from[key]
154 }
155}