Skip to content

Commit 44380b0

Browse files
yyx990803lovelope
authored andcommitted
fix(core): handle edge cases for functional component returning arrays
fix vuejs#7282
1 parent 5b25c23 commit 44380b0

File tree

4 files changed

+80
-12
lines changed

4 files changed

+80
-12
lines changed

src/core/vdom/create-component.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ export function createComponent (
107107
context: Component,
108108
children: ?Array<VNode>,
109109
tag?: string
110-
): VNode | void {
110+
): VNode | Array<VNode> | void {
111111
if (isUndef(Ctor)) {
112112
return
113113
}

src/core/vdom/create-element.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export function createElement (
3030
children: any,
3131
normalizationType: any,
3232
alwaysNormalize: boolean
33-
): VNode {
33+
): VNode | Array<VNode> {
3434
if (Array.isArray(data) || isPrimitive(data)) {
3535
normalizationType = children
3636
children = data
@@ -48,7 +48,7 @@ export function _createElement (
4848
data?: VNodeData,
4949
children?: any,
5050
normalizationType?: number
51-
): VNode {
51+
): VNode | Array<VNode> {
5252
if (isDef(data) && isDef((data: any).__ob__)) {
5353
process.env.NODE_ENV !== 'production' && warn(
5454
`Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
@@ -117,7 +117,9 @@ export function _createElement (
117117
vnode = createComponent(tag, data, context, children)
118118
}
119119
if (isDef(vnode)) {
120-
if (ns) applyNS(vnode, ns)
120+
if (ns && !Array.isArray(vnode)) {
121+
applyNS(vnode, ns)
122+
}
121123
return vnode
122124
} else {
123125
return createEmptyVNode()

src/core/vdom/create-functional-component.js

+18-8
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import VNode from './vnode'
44
import { createElement } from './create-element'
55
import { resolveInject } from '../instance/inject'
6+
import { normalizeChildren } from '../vdom/helpers/normalize-children'
67
import { resolveSlots } from '../instance/render-helpers/resolve-slots'
78
import { installRenderHelpers } from '../instance/render-helpers/index'
89

@@ -47,8 +48,8 @@ function FunctionalRenderContext (
4748

4849
if (options._scopeId) {
4950
this._c = (a, b, c, d) => {
50-
const vnode: ?VNode = createElement(contextVm, a, b, c, d, needNormalization)
51-
if (vnode) {
51+
const vnode = createElement(contextVm, a, b, c, d, needNormalization)
52+
if (vnode && !Array.isArray(vnode)) {
5253
vnode.fnScopeId = options._scopeId
5354
vnode.fnContext = parent
5455
}
@@ -67,7 +68,7 @@ export function createFunctionalComponent (
6768
data: VNodeData,
6869
contextVm: Component,
6970
children: ?Array<VNode>
70-
): VNode | void {
71+
): VNode | Array<VNode> | void {
7172
const options = Ctor.options
7273
const props = {}
7374
const propOptions = options.props
@@ -91,14 +92,23 @@ export function createFunctionalComponent (
9192
const vnode = options.render.call(null, renderContext._c, renderContext)
9293

9394
if (vnode instanceof VNode) {
94-
vnode.fnContext = contextVm
95-
vnode.fnOptions = options
96-
if (data.slot) {
97-
(vnode.data || (vnode.data = {})).slot = data.slot
95+
setFunctionalContextForVNode(vnode, data, contextVm, options)
96+
return vnode
97+
} else if (Array.isArray(vnode)) {
98+
const vnodes = normalizeChildren(vnode) || []
99+
for (let i = 0; i < vnodes.length; i++) {
100+
setFunctionalContextForVNode(vnodes[i], data, contextVm, options)
98101
}
102+
return vnodes
99103
}
104+
}
100105

101-
return vnode
106+
function setFunctionalContextForVNode (vnode, data, vm, options) {
107+
vnode.fnContext = vm
108+
vnode.fnOptions = options
109+
if (data.slot) {
110+
(vnode.data || (vnode.data = {})).slot = data.slot
111+
}
102112
}
103113

104114
function mergeProps (to, from) {

test/unit/features/options/functional.spec.js

+56
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,62 @@ describe('Options functional', () => {
186186
expect(vnode).toEqual(createEmptyVNode())
187187
})
188188

189+
// #7282
190+
it('should normalize top-level arrays', () => {
191+
const Foo = {
192+
functional: true,
193+
render (h) {
194+
return [h('span', 'hi'), null]
195+
}
196+
}
197+
const vm = new Vue({
198+
template: `<div><foo/></div>`,
199+
components: { Foo }
200+
}).$mount()
201+
expect(vm.$el.innerHTML).toBe('<span>hi</span>')
202+
})
203+
204+
it('should work when used as named slot and returning array', () => {
205+
const Foo = {
206+
template: `<div><slot name="test"/></div>`
207+
}
208+
209+
const Bar = {
210+
functional: true,
211+
render: h => ([
212+
h('div', 'one'),
213+
h('div', 'two'),
214+
h(Baz)
215+
])
216+
}
217+
218+
const Baz = {
219+
functional: true,
220+
render: h => h('div', 'three')
221+
}
222+
223+
const vm = new Vue({
224+
template: `<foo><bar slot="test"/></foo>`,
225+
components: { Foo, Bar }
226+
}).$mount()
227+
228+
expect(vm.$el.innerHTML).toBe('<div>one</div><div>two</div><div>three</div>')
229+
})
230+
231+
it('should apply namespace when returning arrays', () => {
232+
const Child = {
233+
functional: true,
234+
render: h => ([h('foo'), h('bar')])
235+
}
236+
const vm = new Vue({
237+
template: `<svg><child/></svg>`,
238+
components: { Child }
239+
}).$mount()
240+
241+
expect(vm.$el.childNodes[0].namespaceURI).toContain('svg')
242+
expect(vm.$el.childNodes[1].namespaceURI).toContain('svg')
243+
})
244+
189245
it('should work with render fns compiled from template', done => {
190246
// code generated via vue-template-es2015-compiler
191247
var render = function (_h, _vm) {

0 commit comments

Comments
 (0)