diff --git a/packages/runtime-core/src/apiDefineComponent.ts b/packages/runtime-core/src/apiDefineComponent.ts index 6561c1c7606..ea53f96ad11 100644 --- a/packages/runtime-core/src/apiDefineComponent.ts +++ b/packages/runtime-core/src/apiDefineComponent.ts @@ -15,7 +15,11 @@ import { import { ExtractPropTypes, ComponentPropsOptions } from './componentProps' import { EmitsOptions } from './componentEmits' import { isFunction } from '@vue/shared' -import { VNodeProps } from './vnode' +import { + VNodeProps, + AllowedComponentProps, + ComponentCustomProps +} from './vnode' // defineComponent is a utility that is primarily used for type inference // when declaring components. Type inference is provided in the component @@ -40,7 +44,7 @@ export function defineComponent( {}, {}, // public props - VNodeProps & Props + VNodeProps & Props & AllowedComponentProps & ComponentCustomProps > > & FunctionalComponent @@ -80,7 +84,7 @@ export function defineComponent< Mixin, Extends, E, - VNodeProps & Props + VNodeProps & Props & AllowedComponentProps & ComponentCustomProps > > & ComponentOptionsWithoutProps< @@ -131,7 +135,8 @@ export function defineComponent< M, Mixin, Extends, - E + E, + AllowedComponentProps & ComponentCustomProps > > & ComponentOptionsWithArrayProps< @@ -182,7 +187,7 @@ export function defineComponent< Mixin, Extends, E, - VNodeProps + VNodeProps & AllowedComponentProps & ComponentCustomProps > > & ComponentOptionsWithObjectProps< diff --git a/packages/runtime-core/src/components/Suspense.ts b/packages/runtime-core/src/components/Suspense.ts index be725ffced7..89ed331b0b7 100644 --- a/packages/runtime-core/src/components/Suspense.ts +++ b/packages/runtime-core/src/components/Suspense.ts @@ -197,7 +197,7 @@ function patchSuspense( } export interface SuspenseBoundary { - vnode: VNode + vnode: VNode parent: SuspenseBoundary | null parentComponent: ComponentInternalInstance | null isSVG: boolean diff --git a/packages/runtime-core/src/components/Teleport.ts b/packages/runtime-core/src/components/Teleport.ts index 9d8034684df..ceeb451df0a 100644 --- a/packages/runtime-core/src/components/Teleport.ts +++ b/packages/runtime-core/src/components/Teleport.ts @@ -11,6 +11,8 @@ import { VNode, VNodeArrayChildren, VNodeProps } from '../vnode' import { isString, ShapeFlags } from '@vue/shared' import { warn } from '../warning' +export type TeleportVNode = VNode + export interface TeleportProps { to: string | RendererElement disabled?: boolean @@ -55,8 +57,8 @@ const resolveTarget = ( export const TeleportImpl = { __isTeleport: true, process( - n1: VNode | null, - n2: VNode, + n1: TeleportVNode | null, + n2: TeleportVNode, container: RendererElement, anchor: RendererNode | null, parentComponent: ComponentInternalInstance | null, @@ -85,10 +87,7 @@ export const TeleportImpl = { insert(placeholder, container, anchor) insert(mainAnchor, container, anchor) - const target = (n2.target = resolveTarget( - n2.props as TeleportProps, - querySelector - )) + const target = (n2.target = resolveTarget(n2.props, querySelector)) const targetAnchor = (n2.targetAnchor = createText('')) if (target) { insert(targetAnchor, target) @@ -165,7 +164,7 @@ export const TeleportImpl = { // target changed if ((n2.props && n2.props.to) !== (n1.props && n1.props.to)) { const nextTarget = (n2.target = resolveTarget( - n2.props as TeleportProps, + n2.props, querySelector )) if (nextTarget) { @@ -267,7 +266,7 @@ interface TeleportTargetElement extends Element { function hydrateTeleport( node: Node, - vnode: VNode, + vnode: TeleportVNode, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, optimized: boolean, @@ -284,7 +283,7 @@ function hydrateTeleport( ) => Node | null ): Node | null { const target = (vnode.target = resolveTarget( - vnode.props as TeleportProps, + vnode.props, querySelector )) if (target) { diff --git a/packages/runtime-core/src/h.ts b/packages/runtime-core/src/h.ts index a00e465bdf7..42d9c971438 100644 --- a/packages/runtime-core/src/h.ts +++ b/packages/runtime-core/src/h.ts @@ -50,7 +50,7 @@ type RawProps = VNodeProps & { __v_isVNode?: never // used to differ from Array children [Symbol.iterator]?: never -} +} & { [key: string]: any } type RawChildren = | string diff --git a/packages/runtime-core/src/hydration.ts b/packages/runtime-core/src/hydration.ts index 81b15879887..aa92891d4b2 100644 --- a/packages/runtime-core/src/hydration.ts +++ b/packages/runtime-core/src/hydration.ts @@ -18,7 +18,7 @@ import { SuspenseBoundary, queueEffectWithSuspense } from './components/Suspense' -import { TeleportImpl } from './components/Teleport' +import { TeleportImpl, TeleportVNode } from './components/Teleport' export type RootHydrateFunction = ( vnode: VNode, @@ -202,7 +202,7 @@ export function createHydrationFunctions( } else { nextNode = (vnode.type as typeof TeleportImpl).hydrate( node, - vnode, + vnode as TeleportVNode, parentComponent, parentSuspense, optimized, diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 4ab1b396cb4..39de3cca2a3 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -54,7 +54,7 @@ export { h } from './h' // Advanced render function utilities export { createVNode, cloneVNode, mergeProps, isVNode } from './vnode' // VNode types -export { Fragment, Text, Comment, Static } from './vnode' +export { Fragment, Text, Comment, Static, ComponentCustomProps } from './vnode' // Built-in components export { Teleport, TeleportProps } from './components/Teleport' export { Suspense, SuspenseProps } from './components/Suspense' diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index d160bdd078c..7f74d434326 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -53,7 +53,7 @@ import { queueEffectWithSuspense, SuspenseImpl } from './components/Suspense' -import { TeleportImpl } from './components/Teleport' +import { TeleportImpl, TeleportVNode } from './components/Teleport' import { isKeepAlive, KeepAliveContext } from './components/KeepAlive' import { registerHMR, unregisterHMR, isHmrUpdating } from './hmr' import { @@ -477,8 +477,8 @@ function baseCreateRenderer( ) } else if (shapeFlag & ShapeFlags.TELEPORT) { ;(type as typeof TeleportImpl).process( - n1, - n2, + n1 as TeleportVNode, + n2 as TeleportVNode, container, anchor, parentComponent, diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index a96519cb94c..30b5c240ad4 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -71,8 +71,14 @@ export type VNodeHook = | VNodeMountHook[] | VNodeUpdateHook[] -export interface VNodeProps { - [key: string]: any +export interface ComponentCustomProps {} +export interface AllowedComponentProps { + class?: unknown + style?: unknown +} + +// https://github.com/microsoft/TypeScript/issues/33099 +export type VNodeProps = { key?: string | number ref?: VNodeRef @@ -104,7 +110,11 @@ export type VNodeNormalizedChildren = | RawSlots | null -export interface VNode { +export interface VNode< + HostNode = RendererNode, + HostElement = RendererElement, + ExtraProps = { [key: string]: any } +> { /** * @internal */ @@ -114,7 +124,7 @@ export interface VNode { */ __v_skip: true type: VNodeTypes - props: VNodeProps | null + props: (VNodeProps & ExtraProps) | null key: string | number | null ref: VNodeNormalizedRef | null scopeId: string | null // SFC only @@ -597,7 +607,7 @@ export function mergeProps(...args: (Data & VNodeProps)[]) { const incoming = toMerge[key] if (existing !== incoming) { ret[key] = existing - ? [].concat(existing as any, toMerge[key]) + ? [].concat(existing as any, toMerge[key] as any) : incoming } } else { diff --git a/test-dts/componentTypeExtensions.test-d.ts b/test-dts/componentTypeExtensions.test-d.ts deleted file mode 100644 index 1d543ac9168..00000000000 --- a/test-dts/componentTypeExtensions.test-d.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { defineComponent, expectError, expectType } from './index' - -declare module '@vue/runtime-core' { - interface ComponentCustomOptions { - test?(n: number): void - } - - interface ComponentCustomProperties { - state: 'stopped' | 'running' - } -} - -export const Custom = defineComponent({ - data: () => ({ counter: 0 }), - - test(n) { - expectType(n) - }, - - methods: { - aMethod() { - // @ts-expect-error - expectError(this.notExisting) - this.counter++ - this.state = 'running' - // @ts-expect-error - expectError((this.state = 'not valid')) - } - } -}) diff --git a/test-dts/componentTypeExtensions.test-d.tsx b/test-dts/componentTypeExtensions.test-d.tsx new file mode 100644 index 00000000000..32a72f844e6 --- /dev/null +++ b/test-dts/componentTypeExtensions.test-d.tsx @@ -0,0 +1,57 @@ +import { defineComponent, expectError, expectType } from './index' + +declare module '@vue/runtime-core' { + interface ComponentCustomOptions { + test?(n: number): void + } + + interface ComponentCustomProperties { + state: 'stopped' | 'running' + } + + interface ComponentCustomProps { + custom?: number + } +} + +export const Custom = defineComponent({ + props: { + bar: String, + baz: { + type: Number, + required: true + } + }, + + data: () => ({ counter: 0 }), + + test(n) { + expectType(n) + }, + + methods: { + aMethod() { + // @ts-expect-error + expectError(this.notExisting) + this.counter++ + this.state = 'running' + // @ts-expect-error + expectError((this.state = 'not valid')) + } + } +}) + +expectType() +expectType() +expectType() + +// @ts-expect-error +expectType() +// @ts-expect-error +expectError() +// @ts-expect-error +expectError() +// @ts-expect-error +expectError() +// @ts-expect-error +expectError() diff --git a/test-dts/defineComponent.test-d.tsx b/test-dts/defineComponent.test-d.tsx index 6bfd968a7f9..f37c0eb1eaa 100644 --- a/test-dts/defineComponent.test-d.tsx +++ b/test-dts/defineComponent.test-d.tsx @@ -171,8 +171,9 @@ describe('with object props', () => { eee={() => ({ a: 'eee' })} fff={(a, b) => ({ a: a > +b })} hhh={false} - // should allow extraneous as attrs + // should allow class/style as attrs class="bar" + style={{ color: 'red' }} // should allow key key={'foo'} // should allow ref