diff --git a/packages/runtime-core/src/apiDefineComponent.ts b/packages/runtime-core/src/apiDefineComponent.ts index 333d18a30f3..0d6b749042d 100644 --- a/packages/runtime-core/src/apiDefineComponent.ts +++ b/packages/runtime-core/src/apiDefineComponent.ts @@ -6,7 +6,8 @@ import { ComponentOptionsWithObjectProps, ComponentOptionsMixin, RenderFunction, - ComponentOptionsBase + ComponentOptionsBase, + ComponentPropsOverride } from './componentOptions' import { SetupContext, @@ -16,7 +17,8 @@ import { import { ExtractPropTypes, ComponentPropsOptions, - ExtractDefaultPropTypes + ExtractDefaultPropTypes, + PropType } from './componentProps' import { EmitsOptions } from './componentEmits' import { isFunction } from '@vue/shared' @@ -43,35 +45,39 @@ export type DefineComponent< PP = PublicProps, Props = Readonly>, Defaults = ExtractDefaultPropTypes -> = ComponentPublicInstanceConstructor< - CreateComponentPublicInstance< - Props, - RawBindings, - D, - C, - M, - Mixin, - Extends, - E, - PP & Props, - Defaults, - true - > & - Props -> & - ComponentOptionsBase< - Props, - RawBindings, - D, - C, - M, - Mixin, - Extends, - E, - EE, - Defaults - > & - PP +> = + // If props is a class we should ifnore all the process + (PropsOrPropOptions extends { prototype: ComponentPropsOverride } + ? PropsOrPropOptions + : ComponentPublicInstanceConstructor< + CreateComponentPublicInstance< + Props, + RawBindings, + D, + C, + M, + Mixin, + Extends, + E, + PP & Props, + Defaults, + true + > & + Props + >) & + ComponentOptionsBase< + Props, + RawBindings, + D, + C, + M, + Mixin, + Extends, + E, + EE, + Defaults + > & + PP // defineComponent is a utility that is primarily used for type inference // when declaring components. Type inference is provided in the component @@ -179,6 +185,34 @@ export function defineComponent< > ): DefineComponent +// overload 5: Allow overriding Props object +export function defineComponent< + O extends { prototype: ComponentPropsOverride }, + RawBindings = {}, + D = {}, + C extends ComputedOptions = ComputedOptions, + M extends MethodOptions = MethodOptions, + Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, + Extends extends ComponentOptionsMixin = ComponentOptionsMixin, + E extends EmitsOptions = Record, + EE extends string = string, + PP = PublicProps +>( + options: ComponentOptionsWithObjectProps< + O extends new () => { $props: infer P } + ? { [K in keyof P]: PropType } + : {}, + RawBindings, + D, + C, + M, + Mixin, + Extends, + E, + EE + > +): DefineComponent + // implementation, close to no-op export function defineComponent(options: unknown) { return isFunction(options) ? { setup: options, name: options.name } : options diff --git a/packages/runtime-core/src/componentOptions.ts b/packages/runtime-core/src/componentOptions.ts index 7b4081afecc..4c378d87693 100644 --- a/packages/runtime-core/src/componentOptions.ts +++ b/packages/runtime-core/src/componentOptions.ts @@ -190,6 +190,10 @@ export interface ComponentOptionsBase< __defaults?: Defaults } +export abstract class ComponentPropsOverride { + readonly $props: Props = {} as Props +} + export type ComponentOptionsWithoutProps< Props = {}, RawBindings = {}, diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 6026d4c248d..a0bf67b9eeb 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -177,6 +177,7 @@ export { ComponentOptionsWithArrayProps, ComponentCustomOptions, ComponentOptionsBase, + ComponentPropsOverride, RenderFunction, MethodOptions, ComputedOptions diff --git a/test-dts/defineComponent.test-d.tsx b/test-dts/defineComponent.test-d.tsx index ff04a0d6f3f..caf235df7cd 100644 --- a/test-dts/defineComponent.test-d.tsx +++ b/test-dts/defineComponent.test-d.tsx @@ -12,6 +12,7 @@ import { ComponentOptions, SetupContext, IsUnion, + ComponentPropsOverride, h } from './index' @@ -1038,6 +1039,38 @@ describe('async setup', () => { vm.a = 2 }) +// #3102 +describe('Generic props', () => { + type OnChange = Clearable extends true + ? (value: ValueType | null) => void + : (value: ValueType) => void + + interface GenericProp { + clearable?: Clearable + value?: ValueType + onChange?: OnChange + } + + class CompProps< + Clearable extends boolean, + ValueType extends string | number | null | undefined + > extends ComponentPropsOverride> {} + + const Comp = defineComponent({ + props: { + value: Object, + clearable: Boolean + } + }) + ; { + expectType<'sss' | null>(a) + }} + /> +}) + // check if defineComponent can be exported export default { // function components diff --git a/test-dts/h.test-d.ts b/test-dts/h.test-d.ts index c71b54a2aa8..2859da62cdc 100644 --- a/test-dts/h.test-d.ts +++ b/test-dts/h.test-d.ts @@ -9,7 +9,8 @@ import { Component, expectError, expectAssignable, - resolveComponent + resolveComponent, + ComponentPropsOverride } from './index' describe('h inference w/ element', () => { @@ -233,3 +234,34 @@ describe('resolveComponent should work', () => { message: '1' }) }) + +// #3102 +describe('Class component generic props', () => { + type OnChange = Clearable extends true + ? (value: ValueType | null) => void + : (value: ValueType) => void + + interface GenericProp { + clearable?: Clearable + value?: ValueType + onChange?: OnChange + } + + class CompProps< + Clearable extends boolean, + ValueType extends string | number | null | undefined + > extends ComponentPropsOverride> {} + + const Comp = defineComponent({ + props: {}, + setup() { + return {} + } + }) + + h(Comp, { + clearable: true, + value: 'test', + onChange(e) {} + }) +})