diff --git a/packages-private/dts-test/defineComponent.test-d.tsx b/packages-private/dts-test/defineComponent.test-d.tsx index fda3ca4856c..266490c19c3 100644 --- a/packages-private/dts-test/defineComponent.test-d.tsx +++ b/packages-private/dts-test/defineComponent.test-d.tsx @@ -321,11 +321,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 }, }, @@ -1062,7 +1060,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') @@ -1261,7 +1259,7 @@ describe('should allow to assign props', () => { const Parent = defineComponent({ props: { - ...Child.props, + ...Child.props!, foo: String, }, }) @@ -1638,37 +1636,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', () => { @@ -1852,7 +1830,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: { @@ -1893,8 +1871,8 @@ declare const ErrorMessage: { unknown, {}, {}, - vue.ComponentOptionsMixin, - vue.ComponentOptionsMixin, + {}, + {}, {}, vue.VNodeProps & vue.AllowedComponentProps & @@ -1916,58 +1894,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 @@ -2013,8 +1940,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..fc942bbe054 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, @@ -1060,7 +1061,7 @@ describe('api: options', () => { }, data() { return { - plusOne: (this as any).count + 1, + plusOne: this.count + 1, } }, computed: { @@ -1414,7 +1415,7 @@ describe('api: options', () => { }) test('computed with setter and no getter', () => { - const Comp = { + const Comp: ComponentOptions = { computed: { foo: { set() {}, @@ -1430,7 +1431,7 @@ describe('api: options', () => { test('assigning to computed with no setter', () => { let instance: any - const Comp = { + const Comp: ComponentOptions = { computed: { foo: { get() {}, @@ -1482,7 +1483,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 b8eb0e47208..547854d3851 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 2ce870f0141..133b0dd600d 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,16 +24,9 @@ import type { ComponentProvideOptions, ComputedOptions, MethodOptions, + ObjectInjectOptions, RenderFunction, } from './componentOptions' -import type { - AllowedComponentProps, - Component, - ComponentCustomProps, - GlobalComponents, - GlobalDirectives, - SetupContext, -} from './component' import type { ComponentObjectPropsOptions, ComponentPropsOptions, @@ -23,30 +34,38 @@ import type { 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, + ExtractMixinEmits, } 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 +73,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 +88,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(): D + props: PropsOrPropOptions extends ComponentPropsOptions + ? PropsOrPropOptions + : {} + computed: C + methods: M + mixins: Mixin[] + extends: Extends + emits: 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< + ExtractMixinEmits & + ExtractMixinEmits & + 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'] + + /** + * #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 +236,8 @@ export type DefineSetupFnComponent< {}, {}, {}, - ComponentOptionsMixin, - ComponentOptionsMixin, + {}, + {}, E, PP, {}, @@ -136,8 +246,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 +277,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 +293,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 +310,64 @@ 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, + 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< + ExtractMixinEmits & + ExtractMixinEmits & + ResolvedEmits & + TypeEmitsToOptions< + string[] extends NormalizedEmits + ? TypeEmits & {} + : ResolvedTypeEmits & {} + > + > + >, >( options: { - props?: (RuntimePropsOptions & ThisType) | RuntimePropsKeys[] + props?: ComponentObjectPropsOptions | RawPropsOptions | _PropsKeys[] /** * @private for language-tools use only */ @@ -237,27 +385,36 @@ export function defineComponent< */ __typeEl?: TypeEl } & 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, - Exposed - > + {}, + {}, + 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 + __defaults: unknown + __typeProps: TypeProps + __typeEmits: TypeEmits + __typeRefs: TypeRefs + __typeEl: TypeEl +}> // 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 2ddaeb509ad..0379fc10488 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 `