diff --git a/packages-private/dts-test/defineComponent.test-d.tsx b/packages-private/dts-test/defineComponent.test-d.tsx index 9dc5bf87135..99b746b4a67 100644 --- a/packages-private/dts-test/defineComponent.test-d.tsx +++ b/packages-private/dts-test/defineComponent.test-d.tsx @@ -350,11 +350,9 @@ describe('with object props', () => { myProp: { type: Number, validator(val: unknown): boolean { - // @ts-expect-error return val !== this.otherProp }, default(): number { - // @ts-expect-error return this.otherProp + 1 }, }, @@ -1091,7 +1089,7 @@ describe('emits', () => { }) // emit should be valid when ComponentPublicInstance is used. - const instance = {} as ComponentPublicInstance + const instance = {} as ComponentPublicInstance<{}, {}, {}, {}, {}, string[]> instance.$emit('test', 1) instance.$emit('test') @@ -1290,7 +1288,7 @@ describe('should allow to assign props', () => { const Parent = defineComponent({ props: { - ...Child.props, + ...Child.props!, foo: String, }, }) @@ -1667,37 +1665,17 @@ describe('expose typing', () => { }) import type { - AllowedComponentProps, - ComponentCustomProps, ComponentInstance, - ComponentOptionsMixin, DefineComponent, Directive, - EmitsOptions, - ExtractPropTypes, KeepAliveProps, TransitionProps, - VNodeProps, vShow, } from 'vue' // code generated by tsc / vue-tsc, make sure this continues to work // so we don't accidentally change the args order of DefineComponent -declare const MyButton: DefineComponent< - {}, - () => JSX.Element, - {}, - {}, - {}, - ComponentOptionsMixin, - ComponentOptionsMixin, - EmitsOptions, - string, - VNodeProps & AllowedComponentProps & ComponentCustomProps, - Readonly>, - {}, - {} -> +declare const MyButton: DefineComponent<{}, () => JSX.Element> ; describe('__typeProps backdoor for union type for conditional props', () => { @@ -1881,7 +1859,7 @@ interface ErrorMessageSlotProps { * relying on legacy CreateComponentPublicInstance signature */ declare const ErrorMessage: { - new (...args: any[]): vue.CreateComponentPublicInstance< + new (...args: any[]): vue.CreateComponentPublicInstanceWithMixins< Readonly< vue.ExtractPropTypes<{ as: { @@ -1922,8 +1900,8 @@ declare const ErrorMessage: { unknown, {}, {}, - vue.ComponentOptionsMixin, - vue.ComponentOptionsMixin, + {}, + {}, {}, vue.VNodeProps & vue.AllowedComponentProps & @@ -1945,58 +1923,7 @@ declare const ErrorMessage: { }, true, {}, - {}, - { - P: {} - B: {} - D: {} - C: {} - M: {} - Defaults: {} - }, - Readonly< - vue.ExtractPropTypes<{ - as: { - type: StringConstructor - default: any - } - name: { - type: StringConstructor - required: true - } - }> - >, - () => - | VNode< - vue.RendererNode, - vue.RendererElement, - { - [key: string]: any - } - > - | vue.Slot - | VNode< - vue.RendererNode, - vue.RendererElement, - { - [key: string]: any - } - >[] - | { - default: () => VNode< - vue.RendererNode, - vue.RendererElement, - { - [key: string]: any - } - >[] - }, - {}, - {}, - {}, - { - as: string - } + {} > __isFragment?: never __isTeleport?: never @@ -2042,8 +1969,8 @@ declare const ErrorMessage: { unknown, {}, {}, - vue.ComponentOptionsMixin, - vue.ComponentOptionsMixin, + {}, + {}, {}, string, { diff --git a/packages-private/dts-test/functionalComponent.test-d.tsx b/packages-private/dts-test/functionalComponent.test-d.tsx index 04eda68b50a..253f1d79a84 100644 --- a/packages-private/dts-test/functionalComponent.test-d.tsx +++ b/packages-private/dts-test/functionalComponent.test-d.tsx @@ -59,14 +59,14 @@ expectType( {}} />) // @ts-expect-error ; -const Baz: FunctionalComponent<{}, string[]> = (props, { emit }) => { +const Baz: FunctionalComponent = (props, { emit }) => { expectType<{}>(props) expectType<(event: string) => void>(emit) } expectType(Baz) -const Qux: FunctionalComponent<{}, ['foo', 'bar']> = (props, { emit }) => { +const Qux: FunctionalComponent<{}, ('foo' | 'bar')[]> = (props, { emit }) => { emit('foo') emit('foo', 1, 2) emit('bar') @@ -77,7 +77,7 @@ expectType(Qux) const Quux: FunctionalComponent< {}, - {}, + string[], { default: { foo: number } optional?: { foo: number } diff --git a/packages/runtime-core/__tests__/apiOptions.spec.ts b/packages/runtime-core/__tests__/apiOptions.spec.ts index 1d4e805efce..56746a61702 100644 --- a/packages/runtime-core/__tests__/apiOptions.spec.ts +++ b/packages/runtime-core/__tests__/apiOptions.spec.ts @@ -3,6 +3,7 @@ */ import type { Mock } from 'vitest' import { + type ComponentOptions, type TestElement, computed, createApp, @@ -930,6 +931,18 @@ describe('api: options', () => { expect(calls).toEqual(['base', 'mixin', 'comp']) }) + test('extends type props', () => { + const Base = defineComponent({ + __typeProps: {} as { a: number }, + }) + defineComponent({ + extends: Base, + render() { + return `${this.a}` + }, + }) + }) + test('beforeCreate/created in extends and mixins', () => { const calls: string[] = [] const BaseA = { @@ -1060,7 +1073,7 @@ describe('api: options', () => { }, data() { return { - plusOne: (this as any).count + 1, + plusOne: this.count + 1, } }, computed: { @@ -1414,7 +1427,7 @@ describe('api: options', () => { }) test('computed with setter and no getter', () => { - const Comp = { + const Comp: ComponentOptions = { computed: { foo: { set() {}, @@ -1430,7 +1443,7 @@ describe('api: options', () => { test('assigning to computed with no setter', () => { let instance: any - const Comp = { + const Comp: ComponentOptions = { computed: { foo: { get() {}, @@ -1482,7 +1495,7 @@ describe('api: options', () => { }) test('methods property is not a function', () => { - const Comp = { + const Comp: ComponentOptions = { methods: { foo: 1, }, diff --git a/packages/runtime-core/__tests__/componentProps.spec.ts b/packages/runtime-core/__tests__/componentProps.spec.ts index ffae1085cd8..7f9ba9574c4 100644 --- a/packages/runtime-core/__tests__/componentProps.spec.ts +++ b/packages/runtime-core/__tests__/componentProps.spec.ts @@ -152,8 +152,11 @@ describe('component props', () => { render( h(Comp, { // absent should cast to false + // @ts-expect-error bar: '', // empty string should cast to true + // @ts-expect-error baz: 'baz', // same string should cast to true + // @ts-expect-error qux: 'ok', // other values should be left in-tact (but raise warning) }), nodeOps.createElement('div'), @@ -206,17 +209,42 @@ describe('component props', () => { expect(proxy.bar).toBe(prevBar) expect(defaultFn).toHaveBeenCalledTimes(1) - render(h(Comp, { bar: { b: 2 } }), root) + render( + h(Comp, { + bar: { + // @ts-expect-error + b: 2, + }, + }), + root, + ) expect(proxy.foo).toBe(1) expect(proxy.bar).toEqual({ b: 2 }) expect(defaultFn).toHaveBeenCalledTimes(1) - render(h(Comp, { foo: 3, bar: { b: 3 } }), root) + render( + h(Comp, { + foo: 3, + bar: { + // @ts-expect-error + b: 3, + }, + }), + root, + ) expect(proxy.foo).toBe(3) expect(proxy.bar).toEqual({ b: 3 }) expect(defaultFn).toHaveBeenCalledTimes(1) - render(h(Comp, { bar: { b: 4 } }), root) + render( + h(Comp, { + bar: { + // @ts-expect-error + b: 4, + }, + }), + root, + ) expect(proxy.foo).toBe(1) expect(proxy.bar).toEqual({ b: 4 }) expect(defaultFn).toHaveBeenCalledTimes(1) @@ -420,13 +448,19 @@ describe('component props', () => { } render( h(Comp, { + // @ts-expect-error bool: 'true', + // @ts-expect-error str: 100, + // @ts-expect-error num: '100', + // @ts-expect-error arr: {}, obj: 'false', cls: {}, + // @ts-expect-error fn: true, + // @ts-expect-error skipCheck: 'foo', empty: [1, 2, 3], }), diff --git a/packages/runtime-core/src/apiDefineComponent.ts b/packages/runtime-core/src/apiDefineComponent.ts index e83715f6a39..04715530305 100644 --- a/packages/runtime-core/src/apiDefineComponent.ts +++ b/packages/runtime-core/src/apiDefineComponent.ts @@ -1,3 +1,21 @@ +import { extend, isFunction } from '@vue/shared' +import type { ComponentTypeEmits } from './apiSetupHelpers' +import type { + AllowedComponentProps, + Component, + ComponentCustomProps, + Data, + GlobalComponents, + GlobalDirectives, + SetupContext, +} from './component' +import type { + EmitsOptions, + EmitsToProps, + ObjectEmitsOptions, + ShortEmitsToObject, + TypeEmitsToOptions, +} from './componentEmits' import type { ComponentInjectOptions, ComponentOptions, @@ -6,47 +24,45 @@ import type { ComponentProvideOptions, ComputedOptions, MethodOptions, + ObjectInjectOptions, RenderFunction, } from './componentOptions' -import type { - AllowedComponentProps, - Component, - ComponentCustomProps, - GlobalComponents, - GlobalDirectives, - SetupContext, -} from './component' import type { ComponentObjectPropsOptions, ComponentPropsOptions, ExtractDefaultPropTypes, ExtractPropTypes, } from './componentProps' -import type { - EmitsOptions, - EmitsToProps, - TypeEmitsToOptions, -} from './componentEmits' -import { type IsKeyValues, extend, isFunction } from '@vue/shared' -import type { VNodeProps } from './vnode' -import type { - ComponentPublicInstanceConstructor, - CreateComponentPublicInstanceWithMixins, -} from './componentPublicInstance' +import type { CreateComponentPublicInstanceWithMixins } from './componentPublicInstance' import type { SlotsType } from './componentSlots' import type { Directive } from './directives' -import type { ComponentTypeEmits } from './apiSetupHelpers' +import type { VNodeProps } from './vnode' export type PublicProps = VNodeProps & AllowedComponentProps & ComponentCustomProps -type ResolveProps = Readonly< - PropsOrPropOptions extends ComponentPropsOptions - ? ExtractPropTypes - : PropsOrPropOptions -> & - ({} extends E ? {} : EmitsToProps) +export interface ComponentOptionsSchema { + setup(): unknown + data(): unknown + props: ComponentPropsOptions + computed: ComputedOptions + methods: MethodOptions + mixins: ComponentOptionsMixin[] + extends: ComponentOptionsMixin + emits: EmitsOptions + slots: SlotsType + inject: ComponentInjectOptions + components: Record + directives: Record + provide: ComponentProvideOptions + expose: string + __defaults: unknown + __typeProps: unknown + __typeEmits: unknown + __typeRefs: Data + __typeEl: Element +} export type DefineComponent< PropsOrPropOptions = {}, @@ -54,12 +70,12 @@ export type DefineComponent< D = {}, C extends ComputedOptions = ComputedOptions, M extends MethodOptions = MethodOptions, - Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, - Extends extends ComponentOptionsMixin = ComponentOptionsMixin, + Mixin extends ComponentOptionsMixin = {}, + Extends extends ComponentOptionsMixin = {}, E extends EmitsOptions = {}, - EE extends string = string, - PP = PublicProps, - Props = ResolveProps, + EE = never, + PP = never, + Props = never, Defaults = ExtractDefaultPropTypes, S extends SlotsType = {}, LC extends Record = {}, @@ -69,52 +85,143 @@ export type DefineComponent< MakeDefaultsOptional extends boolean = true, TypeRefs extends Record = {}, TypeEl extends Element = any, -> = ComponentPublicInstanceConstructor< - CreateComponentPublicInstanceWithMixins< - Props, - RawBindings, - D, - C, - M, +> = InferComponent< + { + setup(): RawBindings + data(): unknown extends D ? {} : D + props: PropsOrPropOptions extends ComponentPropsOptions + ? PropsOrPropOptions + : {} + computed: C + methods: M + mixins: Mixin[] + extends: Extends + emits: Record extends E ? {} : E + slots: S + inject: {} + components: LC + directives: Directives + provide: Provide + expose: Exposed + __defaults: Defaults + __typeProps: PropsOrPropOptions extends ComponentPropsOptions + ? unknown + : PropsOrPropOptions + __typeEmits: unknown + __typeRefs: TypeRefs + __typeEl: TypeEl + }, + MakeDefaultsOptional, + false +> + +export type DefineComponent2 = InferComponent< + T, + // MakeDefaultsOptional - if TypeProps is provided, set to false to use + // user props types verbatim + unknown extends T['__typeProps'] ? true : false, + true +> + +type InferComponent< + T extends ComponentOptionsSchema, + MakeDefaultsOptional extends boolean, + StrictEmits extends boolean, + // resolved types + Mixin extends ComponentOptionsMixin = T['mixins'][number], + Extends extends ComponentOptionsMixin = T['extends'], + ResolvedEmits extends ObjectEmitsOptions = ResolveEmitsOptions< + T['emits'], + T['__typeEmits'] + >, + ResolvedTypeEmits = ResolveTypeEmits, + InferredProps = Readonly< + ExtractPropTypes< + unknown extends T['__typeProps'] + ? T['props'] extends (infer Keys extends string)[] + ? { [K in Keys]: null } + : T['props'] + : {} + > & + T['__typeProps'] & + EmitsToProps< + ResolvedEmits & + TypeEmitsToOptions< + string[] extends T['emits'] + ? T['__typeEmits'] & {} + : ResolvedTypeEmits & {} + > + > + >, +> = ComponentOptionsBase< + any, + ReturnType, + ReturnType, + T['computed'], + T['methods'], + T['mixins'][number] & {}, + T['extends'] & {}, + T['emits'], + never, + never, + {}, + never, + T['slots'], + T['components'] & GlobalComponents, + T['directives'] & GlobalDirectives, + T['expose'], + T['provide'] +> & { + props?: T['props'] + __typeProps?: T['__typeProps'] + __typeEmits?: T['__typeEmits'] + + /** + * #3468 + * + * type-only, used to assist Mixin's type inference, + * typescript will try to simplify the inferred `Mixin` type, + * with the `__differentiator`, typescript won't be able to combine different mixins, + * because the `__differentiator` will be different + */ + __differentiator?: + | keyof ReturnType + | keyof T['computed'] + | keyof T['methods'] + + new ( + ...args: any[] + ): CreateComponentPublicInstanceWithMixins< + InferredProps, + ReturnType, + ReturnType, + T['computed'], + T['methods'], Mixin, Extends, - E, - PP, - Defaults, + ResolvedEmits, + PublicProps, + unknown extends T['__defaults'] + ? ExtractDefaultPropTypes + : T['__defaults'], MakeDefaultsOptional, - {}, - S, - LC & GlobalComponents, - Directives & GlobalDirectives, - Exposed, - TypeRefs, - TypeEl + T['inject'], + T['slots'], + T['components'] & GlobalComponents, + T['directives'] & GlobalDirectives, + T['expose'], + T['__typeRefs'], + T['__typeEl'], + T['provide'], + ResolvedTypeEmits, + StrictEmits, + any > -> & - ComponentOptionsBase< - Props, - RawBindings, - D, - C, - M, - Mixin, - Extends, - E, - EE, - Defaults, - {}, - string, - S, - LC & GlobalComponents, - Directives & GlobalDirectives, - Exposed, - Provide - > & - PP +} export type DefineSetupFnComponent< P extends Record, - E extends EmitsOptions = {}, + E extends EmitsOptions = string[], S extends SlotsType = SlotsType, Props = P & EmitsToProps, PP = PublicProps, @@ -126,8 +233,8 @@ export type DefineSetupFnComponent< {}, {}, {}, - ComponentOptionsMixin, - ComponentOptionsMixin, + {}, + {}, E, PP, {}, @@ -136,8 +243,27 @@ export type DefineSetupFnComponent< S > -type ToResolvedProps = Readonly & - Readonly> +type ResolveEmitsOptions< + RuntimeEmitsOptions extends EmitsOptions, + TypeEmits extends ComponentTypeEmits | unknown, +> = unknown extends TypeEmits + ? RuntimeEmitsOptions extends ObjectEmitsOptions + ? RuntimeEmitsOptions + : {} + : TypeEmits extends Record + ? ShortEmitsToObject + : {} + +type ResolveTypeEmits< + RuntimeEmitsOptions extends EmitsOptions, + TypeEmits extends ComponentTypeEmits | unknown, +> = TypeEmits extends (...args: any[]) => any + ? TypeEmits + : TypeEmits extends Record + ? {} + : RuntimeEmitsOptions extends (infer Keys extends string)[] + ? (event: Keys, ...args: any[]) => void + : {} // defineComponent is a utility that is primarily used for type inference // when declaring components. Type inference is provided in the component @@ -148,7 +274,7 @@ type ToResolvedProps = Readonly & // (uses user defined props interface) export function defineComponent< Props extends Record, - E extends EmitsOptions = {}, + E extends EmitsOptions = string[], EE extends string = string, S extends SlotsType = {}, >( @@ -164,7 +290,7 @@ export function defineComponent< ): DefineSetupFnComponent export function defineComponent< Props extends Record, - E extends EmitsOptions = {}, + E extends EmitsOptions = string[], EE extends string = string, S extends SlotsType = {}, >( @@ -181,45 +307,63 @@ export function defineComponent< // overload 2: defineComponent with options object, infer props from options export function defineComponent< - // props TypeProps, - RuntimePropsOptions extends - ComponentObjectPropsOptions = ComponentObjectPropsOptions, - RuntimePropsKeys extends string = string, - // emits - TypeEmits extends ComponentTypeEmits = {}, - RuntimeEmitsOptions extends EmitsOptions = {}, - RuntimeEmitsKeys extends string = string, - // other options + TypeEmits extends ComponentTypeEmits | unknown = unknown, + TypeRefs extends Record = {}, + TypeEl extends Element = any, + Defaults = unknown, + RawPropsOptions extends ComponentPropsOptions = {}, + RawEmitsOptions extends EmitsOptions = string[], + InjectOptions extends ComponentInjectOptions = {}, Data = {}, SetupBindings = {}, Computed extends ComputedOptions = {}, Methods extends MethodOptions = {}, - Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, - Extends extends ComponentOptionsMixin = ComponentOptionsMixin, - InjectOptions extends ComponentInjectOptions = {}, - InjectKeys extends string = string, + Mixin extends ComponentOptionsMixin = {}, + Extends extends ComponentOptionsMixin = {}, Slots extends SlotsType = {}, LocalComponents extends Record = {}, Directives extends Record = {}, + Provide extends ComponentProvideOptions = {}, Exposed extends string = string, - Provide extends ComponentProvideOptions = ComponentProvideOptions, + // assisted input inference + _PropsKeys extends string = string, + _EmitsKeys extends string = string, + _InjectKeys extends string = string, // resolved types - ResolvedEmits extends EmitsOptions = {} extends RuntimeEmitsOptions - ? TypeEmitsToOptions - : RuntimeEmitsOptions, - InferredProps = IsKeyValues extends true - ? TypeProps - : string extends RuntimePropsKeys - ? ComponentObjectPropsOptions extends RuntimePropsOptions - ? {} - : ExtractPropTypes - : { [key in RuntimePropsKeys]?: any }, - TypeRefs extends Record = {}, - TypeEl extends Element = any, + NormalizedProps extends + ComponentPropsOptions = ComponentPropsOptions extends RawPropsOptions + ? {} + : RawPropsOptions, + NormalizedEmits extends EmitsOptions = EmitsOptions extends RawEmitsOptions + ? string[] + : RawEmitsOptions, + ResolvedEmits extends ObjectEmitsOptions = ResolveEmitsOptions< + NormalizedEmits, + TypeEmits + >, + ResolvedTypeEmits = ResolveTypeEmits, + InferredProps = Readonly< + ExtractPropTypes< + unknown extends TypeProps + ? NormalizedProps extends (infer Keys extends string)[] + ? { [K in Keys]: null } + : NormalizedProps + : {} + > & + TypeProps & + EmitsToProps< + ResolvedEmits & + TypeEmitsToOptions< + string[] extends NormalizedEmits + ? TypeEmits & {} + : ResolvedTypeEmits & {} + > + > + >, >( options: { - props?: (RuntimePropsOptions & ThisType) | RuntimePropsKeys[] + props?: ComponentObjectPropsOptions | RawPropsOptions | _PropsKeys[] /** * @private for language-tools use only */ @@ -236,28 +380,41 @@ export function defineComponent< * @private for language-tools use only */ __typeEl?: TypeEl + /** + * @private for language-tools use only + */ + __defaults?: Defaults } & ComponentOptionsBase< - ToResolvedProps, + InferredProps, SetupBindings, Data, Computed, Methods, Mixin, Extends, - RuntimeEmitsOptions, - RuntimeEmitsKeys, - {}, // Defaults - InjectOptions, - InjectKeys, + ObjectEmitsOptions | (RawEmitsOptions & ThisType) | _EmitsKeys[], + never, + never, + ObjectInjectOptions | InjectOptions | _InjectKeys[], + never, Slots, - LocalComponents, - Directives, + Record | LocalComponents, + Record | Directives, Exposed, - Provide + Provide, + CreateComponentPublicInstanceWithMixins< + InferredProps, + SetupBindings, + {}, + {}, + MethodOptions, + Mixin, + Extends + > > & ThisType< CreateComponentPublicInstanceWithMixins< - ToResolvedProps, + InferredProps, SetupBindings, Data, Computed, @@ -265,40 +422,45 @@ export function defineComponent< Mixin, Extends, ResolvedEmits, - {}, - {}, + {}, // PublicProps + {}, // Defaults false, InjectOptions, Slots, - LocalComponents, - Directives, - string - > + {}, + {}, + string, // Exposed + TypeRefs, + TypeEl, + {}, + ResolvedTypeEmits, + true, + {} + > & { + $options: typeof options + } >, -): DefineComponent< - InferredProps, - SetupBindings, - Data, - Computed, - Methods, - Mixin, - Extends, - ResolvedEmits, - RuntimeEmitsKeys, - PublicProps, - ToResolvedProps, - ExtractDefaultPropTypes, - Slots, - LocalComponents, - Directives, - Exposed, - Provide, - // MakeDefaultsOptional - if TypeProps is provided, set to false to use - // user props types verbatim - unknown extends TypeProps ? true : false, - TypeRefs, - TypeEl -> +): DefineComponent2<{ + setup(): SetupBindings + data(): Data + props: NormalizedProps + computed: Computed + methods: Methods + mixins: Mixin[] + extends: Extends + emits: NormalizedEmits + slots: Slots + inject: {} // omitted + components: LocalComponents + directives: Directives + provide: Provide + expose: Exposed + __typeProps: TypeProps + __typeEmits: TypeEmits + __typeRefs: TypeRefs + __typeEl: TypeEl + __defaults: Defaults +}> // implementation, close to no-op /*@__NO_SIDE_EFFECTS__*/ diff --git a/packages/runtime-core/src/apiSetupHelpers.ts b/packages/runtime-core/src/apiSetupHelpers.ts index 5c77497bc60..752652d4ccc 100644 --- a/packages/runtime-core/src/apiSetupHelpers.ts +++ b/packages/runtime-core/src/apiSetupHelpers.ts @@ -193,8 +193,8 @@ export function defineOptions< D = {}, C extends ComputedOptions = {}, M extends MethodOptions = {}, - Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, - Extends extends ComponentOptionsMixin = ComponentOptionsMixin, + Mixin extends ComponentOptionsMixin = {}, + Extends extends ComponentOptionsMixin = {}, >( options?: ComponentOptionsBase< {}, @@ -206,6 +206,10 @@ export function defineOptions< Extends, {} > & { + /** + * setup should be defined via `