fix(core): handle edge cases for functional component returning arrays

fix #7282
This commit is contained in:
Evan You 2017-12-20 11:02:42 -05:00
parent 49aae6bb15
commit 8335217cb4
4 changed files with 80 additions and 12 deletions

View File

@ -107,7 +107,7 @@ export function createComponent (
context: Component,
children: ?Array<VNode>,
tag?: string
): VNode | void {
): VNode | Array<VNode> | void {
if (isUndef(Ctor)) {
return
}

View File

@ -30,7 +30,7 @@ export function createElement (
children: any,
normalizationType: any,
alwaysNormalize: boolean
): VNode {
): VNode | Array<VNode> {
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<VNode> {
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()

View File

@ -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>
): VNode | void {
): VNode | Array<VNode> | 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) {

View File

@ -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: `<div><foo/></div>`,
components: { Foo }
}).$mount()
expect(vm.$el.innerHTML).toBe('<span>hi</span>')
})
it('should work when used as named slot and returning array', () => {
const Foo = {
template: `<div><slot name="test"/></div>`
}
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: `<foo><bar slot="test"/></foo>`,
components: { Foo, Bar }
}).$mount()
expect(vm.$el.innerHTML).toBe('<div>one</div><div>two</div><div>three</div>')
})
it('should apply namespace when returning arrays', () => {
const Child = {
functional: true,
render: h => ([h('foo'), h('bar')])
}
const vm = new Vue({
template: `<svg><child/></svg>`,
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) {