diff --git a/packages/runtime-core/__tests__/helpers/resolveAssets.spec.ts b/packages/runtime-core/__tests__/helpers/resolveAssets.spec.ts index d8088454..6f8957d0 100644 --- a/packages/runtime-core/__tests__/helpers/resolveAssets.spec.ts +++ b/packages/runtime-core/__tests__/helpers/resolveAssets.spec.ts @@ -8,7 +8,9 @@ import { resolveDynamicComponent, h, serializeInner, - createVNode + createVNode, + Comment, + VNode } from '@vue/runtime-test' import { mockWarn } from '@vue/shared' @@ -102,6 +104,7 @@ describe('resolveAssets', () => { baz: { render: () => 'baz' } } let foo, bar, baz // dynamic components + let dynamicVNode: VNode const Child = { render(this: any) { @@ -115,6 +118,7 @@ describe('resolveAssets', () => { return () => { foo = resolveDynamicComponent('foo') // bar = resolveDynamicComponent(dynamicComponents.bar) // , function + dynamicVNode = createVNode(resolveDynamicComponent(null)) // return h(Child, () => { // check inside child slots baz = resolveDynamicComponent(dynamicComponents.baz) // , object @@ -129,6 +133,8 @@ describe('resolveAssets', () => { expect(foo).toBe(dynamicComponents.foo) expect(bar).toBe(dynamicComponents.bar) 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', () => { diff --git a/packages/runtime-core/src/helpers/resolveAssets.ts b/packages/runtime-core/src/helpers/resolveAssets.ts index 8cb134d5..f7118a88 100644 --- a/packages/runtime-core/src/helpers/resolveAssets.ts +++ b/packages/runtime-core/src/helpers/resolveAssets.ts @@ -6,13 +6,7 @@ import { ComponentOptions } from '../component' import { Directive } from '../directives' -import { - camelize, - capitalize, - isString, - isObject, - isFunction -} from '@vue/shared' +import { camelize, capitalize, isString, isObject } from '@vue/shared' import { warn } from '../warning' const COMPONENTS = 'components' @@ -22,14 +16,16 @@ export function resolveComponent(name: string): Component | string | undefined { return resolveAsset(COMPONENTS, name) || name } +export const NULL_DYNAMIC_COMPONENT = Symbol() + export function resolveDynamicComponent( component: unknown -): Component | string | undefined { - if (!component) return +): Component | string | typeof NULL_DYNAMIC_COMPONENT { if (isString(component)) { return resolveAsset(COMPONENTS, component, false) || component - } else if (isFunction(component) || isObject(component)) { - return component + } else { + // invalid types will fallthrough to createVNode and raise warning + return (component as any) || NULL_DYNAMIC_COMPONENT } } diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index f4de40ef..3b21c6ab 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -31,6 +31,7 @@ import { currentScopeId } from './helpers/scopeId' import { TeleportImpl, isTeleport } from './components/Teleport' import { currentRenderingInstance } from './componentRenderUtils' import { RendererNode, RendererElement } from './renderer' +import { NULL_DYNAMIC_COMPONENT } from './helpers/resolveAssets' export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as { __isFragment: true @@ -254,15 +255,15 @@ export const createVNode = (__DEV__ : _createVNode) as typeof _createVNode function _createVNode( - type: VNodeTypes | ClassComponent, + type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT, props: (Data & VNodeProps) | null = null, children: unknown = null, patchFlag: number = 0, dynamicProps: string[] | null = null, isBlockNode = false ): VNode { - if (!type) { - if (__DEV__) { + if (!type || type === NULL_DYNAMIC_COMPONENT) { + if (__DEV__ && !type) { warn(`Invalid vnode type when creating vnode: ${type}.`) } type = Comment