Skip to content

Commit

Permalink
fix(core): handle edge cases for functional component returning arrays
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 authored and hefeng committed Jan 25, 2019
1 parent 350a1e1 commit 9f01d30
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 12 deletions.
2 changes: 1 addition & 1 deletion src/core/vdom/create-component.js
Expand Up @@ -107,7 +107,7 @@ export function createComponent (
context: Component,
children: ?Array<VNode>,
tag?: string
): VNode | void {
): VNode | Array<VNode> | void {
if (isUndef(Ctor)) {
return
}
Expand Down
8 changes: 5 additions & 3 deletions src/core/vdom/create-element.js
Expand Up @@ -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
Expand All @@ -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` +
Expand Down Expand Up @@ -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()
Expand Down
26 changes: 18 additions & 8 deletions src/core/vdom/create-functional-component.js
Expand Up @@ -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'

Expand Down Expand Up @@ -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
}
Expand All @@ -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
Expand All @@ -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) {
Expand Down
56 changes: 56 additions & 0 deletions test/unit/features/options/functional.spec.js
Expand Up @@ -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) {
Expand Down

0 comments on commit 9f01d30

Please sign in to comment.