fix(runtime-core): dynamic component should support falsy values without warning

This commit is contained in:
Evan You 2020-04-24 15:06:21 -04:00
parent f3a9b516bd
commit ded92f93b4
3 changed files with 18 additions and 15 deletions

View File

@ -8,7 +8,9 @@ import {
resolveDynamicComponent, resolveDynamicComponent,
h, h,
serializeInner, serializeInner,
createVNode createVNode,
Comment,
VNode
} from '@vue/runtime-test' } from '@vue/runtime-test'
import { mockWarn } from '@vue/shared' import { mockWarn } from '@vue/shared'
@ -102,6 +104,7 @@ describe('resolveAssets', () => {
baz: { render: () => 'baz' } baz: { render: () => 'baz' }
} }
let foo, bar, baz // dynamic components let foo, bar, baz // dynamic components
let dynamicVNode: VNode
const Child = { const Child = {
render(this: any) { render(this: any) {
@ -115,6 +118,7 @@ describe('resolveAssets', () => {
return () => { return () => {
foo = resolveDynamicComponent('foo') // <component is="foo"/> foo = resolveDynamicComponent('foo') // <component is="foo"/>
bar = resolveDynamicComponent(dynamicComponents.bar) // <component :is="bar"/>, function bar = resolveDynamicComponent(dynamicComponents.bar) // <component :is="bar"/>, function
dynamicVNode = createVNode(resolveDynamicComponent(null)) // <component :is="null"/>
return h(Child, () => { return h(Child, () => {
// check inside child slots // check inside child slots
baz = resolveDynamicComponent(dynamicComponents.baz) // <component :is="baz"/>, object baz = resolveDynamicComponent(dynamicComponents.baz) // <component :is="baz"/>, object
@ -129,6 +133,8 @@ describe('resolveAssets', () => {
expect(foo).toBe(dynamicComponents.foo) expect(foo).toBe(dynamicComponents.foo)
expect(bar).toBe(dynamicComponents.bar) expect(bar).toBe(dynamicComponents.bar)
expect(baz).toBe(dynamicComponents.baz) expect(baz).toBe(dynamicComponents.baz)
// should allow explicit falsy type to remove the component
expect(dynamicVNode!.type).toBe(Comment)
}) })
test('resolve dynamic component should fallback to plain element without warning', () => { test('resolve dynamic component should fallback to plain element without warning', () => {

View File

@ -6,13 +6,7 @@ import {
ComponentOptions ComponentOptions
} from '../component' } from '../component'
import { Directive } from '../directives' import { Directive } from '../directives'
import { import { camelize, capitalize, isString, isObject } from '@vue/shared'
camelize,
capitalize,
isString,
isObject,
isFunction
} from '@vue/shared'
import { warn } from '../warning' import { warn } from '../warning'
const COMPONENTS = 'components' const COMPONENTS = 'components'
@ -22,14 +16,16 @@ export function resolveComponent(name: string): Component | string | undefined {
return resolveAsset(COMPONENTS, name) || name return resolveAsset(COMPONENTS, name) || name
} }
export const NULL_DYNAMIC_COMPONENT = Symbol()
export function resolveDynamicComponent( export function resolveDynamicComponent(
component: unknown component: unknown
): Component | string | undefined { ): Component | string | typeof NULL_DYNAMIC_COMPONENT {
if (!component) return
if (isString(component)) { if (isString(component)) {
return resolveAsset(COMPONENTS, component, false) || component return resolveAsset(COMPONENTS, component, false) || component
} else if (isFunction(component) || isObject(component)) { } else {
return component // invalid types will fallthrough to createVNode and raise warning
return (component as any) || NULL_DYNAMIC_COMPONENT
} }
} }

View File

@ -31,6 +31,7 @@ import { currentScopeId } from './helpers/scopeId'
import { TeleportImpl, isTeleport } from './components/Teleport' import { TeleportImpl, isTeleport } from './components/Teleport'
import { currentRenderingInstance } from './componentRenderUtils' import { currentRenderingInstance } from './componentRenderUtils'
import { RendererNode, RendererElement } from './renderer' import { RendererNode, RendererElement } from './renderer'
import { NULL_DYNAMIC_COMPONENT } from './helpers/resolveAssets'
export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as { export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
__isFragment: true __isFragment: true
@ -254,15 +255,15 @@ export const createVNode = (__DEV__
: _createVNode) as typeof _createVNode : _createVNode) as typeof _createVNode
function _createVNode( function _createVNode(
type: VNodeTypes | ClassComponent, type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
props: (Data & VNodeProps) | null = null, props: (Data & VNodeProps) | null = null,
children: unknown = null, children: unknown = null,
patchFlag: number = 0, patchFlag: number = 0,
dynamicProps: string[] | null = null, dynamicProps: string[] | null = null,
isBlockNode = false isBlockNode = false
): VNode { ): VNode {
if (!type) { if (!type || type === NULL_DYNAMIC_COMPONENT) {
if (__DEV__) { if (__DEV__ && !type) {
warn(`Invalid vnode type when creating vnode: ${type}.`) warn(`Invalid vnode type when creating vnode: ${type}.`)
} }
type = Comment type = Comment