diff --git a/src/core/vdom/create-component.js b/src/core/vdom/create-component.js index 60809093a14..81dd1a2dc46 100644 --- a/src/core/vdom/create-component.js +++ b/src/core/vdom/create-component.js @@ -107,7 +107,7 @@ export function createComponent ( context: Component, children: ?Array, tag?: string -): VNode | void { +): VNode | Array | void { if (isUndef(Ctor)) { return } diff --git a/src/core/vdom/create-element.js b/src/core/vdom/create-element.js index 9b5b28d2509..d711dfb8421 100644 --- a/src/core/vdom/create-element.js +++ b/src/core/vdom/create-element.js @@ -30,7 +30,7 @@ export function createElement ( children: any, normalizationType: any, alwaysNormalize: boolean -): VNode { +): VNode | Array { if (Array.isArray(data) || isPrimitive(data)) { normalizationType = children children = data @@ -48,7 +48,7 @@ export function _createElement ( data?: VNodeData, children?: any, normalizationType?: number -): VNode { +): VNode | Array { if (isDef(data) && isDef((data: any).__ob__)) { process.env.NODE_ENV !== 'production' && warn( `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` + @@ -117,7 +117,9 @@ export function _createElement ( vnode = createComponent(tag, data, context, children) } if (isDef(vnode)) { - if (ns) applyNS(vnode, ns) + if (ns && !Array.isArray(vnode)) { + applyNS(vnode, ns) + } return vnode } else { return createEmptyVNode() diff --git a/src/core/vdom/create-functional-component.js b/src/core/vdom/create-functional-component.js index ff00f240a22..d0cbd0a2584 100644 --- a/src/core/vdom/create-functional-component.js +++ b/src/core/vdom/create-functional-component.js @@ -3,6 +3,7 @@ import VNode 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 { installRenderHelpers } from '../instance/render-helpers/index' @@ -47,8 +48,8 @@ function FunctionalRenderContext ( if (options._scopeId) { this._c = (a, b, c, d) => { - const vnode: ?VNode = createElement(contextVm, a, b, c, d, needNormalization) - if (vnode) { + const vnode = createElement(contextVm, a, b, c, d, needNormalization) + if (vnode && !Array.isArray(vnode)) { vnode.fnScopeId = options._scopeId vnode.fnContext = parent } @@ -67,7 +68,7 @@ export function createFunctionalComponent ( data: VNodeData, contextVm: Component, children: ?Array -): VNode | void { +): VNode | Array | void { const options = Ctor.options const props = {} const propOptions = options.props @@ -91,14 +92,23 @@ export function createFunctionalComponent ( const vnode = options.render.call(null, renderContext._c, renderContext) if (vnode instanceof VNode) { - vnode.fnContext = contextVm - vnode.fnOptions = options - if (data.slot) { - (vnode.data || (vnode.data = {})).slot = data.slot + setFunctionalContextForVNode(vnode, data, contextVm, options) + return vnode + } else if (Array.isArray(vnode)) { + const vnodes = normalizeChildren(vnode) || [] + for (let i = 0; i < vnodes.length; i++) { + setFunctionalContextForVNode(vnodes[i], data, contextVm, options) } + return vnodes } +} - return vnode +function setFunctionalContextForVNode (vnode, data, vm, options) { + vnode.fnContext = vm + vnode.fnOptions = options + if (data.slot) { + (vnode.data || (vnode.data = {})).slot = data.slot + } } function mergeProps (to, from) { diff --git a/test/unit/features/options/functional.spec.js b/test/unit/features/options/functional.spec.js index ecbd0be3c55..2c6abe95972 100644 --- a/test/unit/features/options/functional.spec.js +++ b/test/unit/features/options/functional.spec.js @@ -186,6 +186,62 @@ describe('Options functional', () => { expect(vnode).toEqual(createEmptyVNode()) }) + // #7282 + it('should normalize top-level arrays', () => { + const Foo = { + functional: true, + render (h) { + return [h('span', 'hi'), null] + } + } + const vm = new Vue({ + template: `
`, + components: { Foo } + }).$mount() + expect(vm.$el.innerHTML).toBe('hi') + }) + + it('should work when used as named slot and returning array', () => { + const Foo = { + template: `
` + } + + const Bar = { + functional: true, + render: h => ([ + h('div', 'one'), + h('div', 'two'), + h(Baz) + ]) + } + + const Baz = { + functional: true, + render: h => h('div', 'three') + } + + const vm = new Vue({ + template: ``, + components: { Foo, Bar } + }).$mount() + + expect(vm.$el.innerHTML).toBe('
one
two
three
') + }) + + it('should apply namespace when returning arrays', () => { + const Child = { + functional: true, + render: h => ([h('foo'), h('bar')]) + } + const vm = new Vue({ + template: ``, + components: { Child } + }).$mount() + + expect(vm.$el.childNodes[0].namespaceURI).toContain('svg') + expect(vm.$el.childNodes[1].namespaceURI).toContain('svg') + }) + it('should work with render fns compiled from template', done => { // code generated via vue-template-es2015-compiler var render = function (_h, _vm) {