feat(types): provide internal options for directly using user types in language tools (#10801)

This commit is contained in:
Evan You 2024-04-27 11:48:37 +08:00 committed by GitHub
parent 4cc9ca870c
commit 75c8cf63a1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 652 additions and 475 deletions

View File

@ -15,7 +15,7 @@ import {
withKeys, withKeys,
withModifiers, withModifiers,
} from 'vue' } from 'vue'
import { type IsUnion, describe, expectType } from './utils' import { type IsAny, type IsUnion, describe, expectType } from './utils'
describe('with object props', () => { describe('with object props', () => {
interface ExpectedProps { interface ExpectedProps {
@ -1623,3 +1623,146 @@ declare const MyButton: DefineComponent<
{} {}
> >
;<MyButton class="x" /> ;<MyButton class="x" />
describe('__typeProps backdoor for union type for conditional props', () => {
interface CommonProps {
size?: 'xl' | 'l' | 'm' | 's' | 'xs'
}
type ConditionalProps =
| {
color?: 'normal' | 'primary' | 'secondary'
appearance?: 'normal' | 'outline' | 'text'
}
| {
color: 'white'
appearance: 'outline'
}
type Props = CommonProps & ConditionalProps
const Comp = defineComponent({
__typeProps: {} as Props,
})
// @ts-expect-error
;<Comp color="white" />
// @ts-expect-error
;<Comp color="white" appearance="normal" />
;<Comp color="white" appearance="outline" />
const c = new Comp()
// @ts-expect-error
c.$props = { color: 'white' }
// @ts-expect-error
c.$props = { color: 'white', appearance: 'text' }
c.$props = { color: 'white', appearance: 'outline' }
})
describe('__typeEmits backdoor, 3.3+ object syntax', () => {
type Emits = {
change: [id: number]
update: [value: string]
}
const Comp = defineComponent({
__typeEmits: {} as Emits,
mounted() {
this.$props.onChange?.(123)
// @ts-expect-error
this.$props.onChange?.('123')
this.$props.onUpdate?.('foo')
// @ts-expect-error
this.$props.onUpdate?.(123)
// @ts-expect-error
this.$emit('foo')
this.$emit('change', 123)
// @ts-expect-error
this.$emit('change', '123')
this.$emit('update', 'test')
// @ts-expect-error
this.$emit('update', 123)
},
})
;<Comp onChange={id => id.toFixed(2)} />
;<Comp onUpdate={id => id.toUpperCase()} />
// @ts-expect-error
;<Comp onChange={id => id.slice(1)} />
// @ts-expect-error
;<Comp onUpdate={id => id.toFixed(2)} />
const c = new Comp()
// @ts-expect-error
c.$emit('foo')
c.$emit('change', 123)
// @ts-expect-error
c.$emit('change', '123')
c.$emit('update', 'test')
// @ts-expect-error
c.$emit('update', 123)
})
describe('__typeEmits backdoor, call signature syntax', () => {
type Emits = {
(e: 'change', id: number): void
(e: 'update', value: string): void
}
const Comp = defineComponent({
__typeEmits: {} as Emits,
mounted() {
this.$props.onChange?.(123)
// @ts-expect-error
this.$props.onChange?.('123')
this.$props.onUpdate?.('foo')
// @ts-expect-error
this.$props.onUpdate?.(123)
// @ts-expect-error
this.$emit('foo')
this.$emit('change', 123)
// @ts-expect-error
this.$emit('change', '123')
this.$emit('update', 'test')
// @ts-expect-error
this.$emit('update', 123)
},
})
;<Comp onChange={id => id.toFixed(2)} />
;<Comp onUpdate={id => id.toUpperCase()} />
// @ts-expect-error
;<Comp onChange={id => id.slice(1)} />
// @ts-expect-error
;<Comp onUpdate={id => id.toFixed(2)} />
const c = new Comp()
// @ts-expect-error
c.$emit('foo')
c.$emit('change', 123)
// @ts-expect-error
c.$emit('change', '123')
c.$emit('update', 'test')
// @ts-expect-error
c.$emit('update', 123)
})
defineComponent({
props: {
foo: [String, null],
},
setup(props) {
expectType<IsAny<typeof props.foo>>(false)
expectType<string | null | undefined>(props.foo)
},
})

View File

@ -3,9 +3,6 @@ import type {
ComponentOptions, ComponentOptions,
ComponentOptionsBase, ComponentOptionsBase,
ComponentOptionsMixin, ComponentOptionsMixin,
ComponentOptionsWithArrayProps,
ComponentOptionsWithObjectProps,
ComponentOptionsWithoutProps,
ComponentProvideOptions, ComponentProvideOptions,
ComputedOptions, ComputedOptions,
MethodOptions, MethodOptions,
@ -25,7 +22,11 @@ import type {
ExtractDefaultPropTypes, ExtractDefaultPropTypes,
ExtractPropTypes, ExtractPropTypes,
} from './componentProps' } from './componentProps'
import type { EmitsOptions, EmitsToProps } from './componentEmits' import type {
EmitsOptions,
EmitsToProps,
TypeEmitsToOptions,
} from './componentEmits'
import { extend, isFunction } from '@vue/shared' import { extend, isFunction } from '@vue/shared'
import type { VNodeProps } from './vnode' import type { VNodeProps } from './vnode'
import type { import type {
@ -34,6 +35,7 @@ import type {
} from './componentPublicInstance' } from './componentPublicInstance'
import type { SlotsType } from './componentSlots' import type { SlotsType } from './componentSlots'
import type { Directive } from './directives' import type { Directive } from './directives'
import type { ComponentTypeEmits } from './apiSetupHelpers'
export type PublicProps = VNodeProps & export type PublicProps = VNodeProps &
AllowedComponentProps & AllowedComponentProps &
@ -64,6 +66,7 @@ export type DefineComponent<
Directives extends Record<string, Directive> = {}, Directives extends Record<string, Directive> = {},
Exposed extends string = string, Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions, Provide extends ComponentProvideOptions = ComponentProvideOptions,
MakeDefaultsOptional extends boolean = true,
> = ComponentPublicInstanceConstructor< > = ComponentPublicInstanceConstructor<
CreateComponentPublicInstance< CreateComponentPublicInstance<
Props, Props,
@ -76,7 +79,7 @@ export type DefineComponent<
E, E,
PP & Props, PP & Props,
Defaults, Defaults,
true, MakeDefaultsOptional,
{}, {},
S, S,
LC & GlobalComponents, LC & GlobalComponents,
@ -169,183 +172,114 @@ export function defineComponent<
}, },
): DefineSetupFnComponent<Props, E, S> ): DefineSetupFnComponent<Props, E, S>
// overload 2: object format with no props // overload 2: defineComponent with options object, infer props from options
// (uses user defined props interface)
// return type is for Vetur and TSX support
export function defineComponent< export function defineComponent<
Props = {}, // props
RawBindings = {}, TypeProps,
D = {}, RuntimePropsOptions extends
C extends ComputedOptions = {}, ComponentObjectPropsOptions = ComponentObjectPropsOptions,
M extends MethodOptions = {}, RuntimePropsKeys extends string = string,
// emits
TypeEmits extends ComponentTypeEmits = {},
RuntimeEmitsOptions extends EmitsOptions = {},
RuntimeEmitsKeys extends string = string,
// other options
Data = {},
SetupBindings = {},
Computed extends ComputedOptions = {},
Methods extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = {}, InjectOptions extends ComponentInjectOptions = {},
EE extends string = string, InjectKeys extends string = string,
I extends ComponentInjectOptions = {}, Slots extends SlotsType = {},
II extends string = string, LocalComponents extends Record<string, Component> = {},
S extends SlotsType = {},
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {}, Directives extends Record<string, Directive> = {},
Exposed extends string = string, Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions, Provide extends ComponentProvideOptions = ComponentProvideOptions,
// resolved types
ResolvedEmits extends EmitsOptions = {} extends RuntimeEmitsOptions
? TypeEmitsToOptions<TypeEmits>
: RuntimeEmitsOptions,
InferredProps = unknown extends TypeProps
? string extends RuntimePropsKeys
? ComponentObjectPropsOptions extends RuntimePropsOptions
? {}
: ExtractPropTypes<RuntimePropsOptions>
: { [key in RuntimePropsKeys]?: any }
: TypeProps,
ResolvedProps = Readonly<InferredProps & EmitsToProps<ResolvedEmits>>,
>( >(
options: ComponentOptionsWithoutProps< options: {
Props, props?: (RuntimePropsOptions & ThisType<void>) | RuntimePropsKeys[]
RawBindings, /**
D, * @private for language-tools use only
C, */
M, __typeProps?: TypeProps
/**
* @private for language-tools use only
*/
__typeEmits?: TypeEmits
} & ComponentOptionsBase<
ResolvedProps,
SetupBindings,
Data,
Computed,
Methods,
Mixin, Mixin,
Extends, Extends,
E, RuntimeEmitsOptions,
EE, RuntimeEmitsKeys,
I, {}, // Defaults
II, InjectOptions,
S, InjectKeys,
LC, Slots,
LocalComponents,
Directives, Directives,
Exposed, Exposed,
Provide Provide
> &
ThisType<
CreateComponentPublicInstance<
ResolvedProps,
SetupBindings,
Data,
Computed,
Methods,
Mixin,
Extends,
ResolvedEmits,
RuntimeEmitsKeys,
{},
false,
InjectOptions,
Slots,
LocalComponents,
Directives,
Exposed
>
>, >,
): DefineComponent< ): DefineComponent<
Props, InferredProps,
RawBindings, SetupBindings,
D, Data,
C, Computed,
M, Methods,
Mixin, Mixin,
Extends, Extends,
E, ResolvedEmits,
EE, RuntimeEmitsKeys,
PublicProps, PublicProps,
ResolveProps<Props, E>, ResolvedProps,
ExtractDefaultPropTypes<Props>, ExtractDefaultPropTypes<RuntimePropsOptions>,
S, Slots,
LC, LocalComponents,
Directives, Directives,
Exposed, Exposed,
Provide Provide,
> // MakeDefaultsOptional - if TypeProps is provided, set to false to use
// user props types verbatim
// overload 3: object format with array props declaration unknown extends TypeProps ? true : false
// props inferred as { [key in PropNames]?: any }
// return type is for Vetur and TSX support
export function defineComponent<
PropNames extends string,
RawBindings,
D,
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = {},
EE extends string = string,
S extends SlotsType = {},
I extends ComponentInjectOptions = {},
II extends string = string,
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
Props = Readonly<{ [key in PropNames]?: any }>,
>(
options: ComponentOptionsWithArrayProps<
PropNames,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
I,
II,
S,
LC,
Directives,
Exposed,
Provide
>,
): DefineComponent<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
PublicProps,
ResolveProps<Props, E>,
ExtractDefaultPropTypes<Props>,
S,
LC,
Directives,
Exposed,
Provide
>
// overload 4: object format with object props declaration
// see `ExtractPropTypes` in ./componentProps.ts
export function defineComponent<
// the Readonly constraint allows TS to treat the type of { required: true }
// as constant instead of boolean.
PropsOptions extends Readonly<ComponentPropsOptions>,
RawBindings,
D,
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = {},
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
>(
options: ComponentOptionsWithObjectProps<
PropsOptions,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
I,
II,
S,
LC,
Directives,
Exposed,
Provide
>,
): DefineComponent<
PropsOptions,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
PublicProps,
ResolveProps<PropsOptions, E>,
ExtractDefaultPropTypes<PropsOptions>,
S,
LC,
Directives,
Exposed,
Provide
> >
// implementation, close to no-op // implementation, close to no-op

View File

@ -16,8 +16,8 @@ import {
} from './component' } from './component'
import type { EmitFn, EmitsOptions, ObjectEmitsOptions } from './componentEmits' import type { EmitFn, EmitsOptions, ObjectEmitsOptions } from './componentEmits'
import type { import type {
ComponentOptionsBase,
ComponentOptionsMixin, ComponentOptionsMixin,
ComponentOptionsWithoutProps,
ComputedOptions, ComputedOptions,
MethodOptions, MethodOptions,
} from './componentOptions' } from './componentOptions'
@ -135,9 +135,11 @@ export function defineEmits<EE extends string = string>(
export function defineEmits<E extends EmitsOptions = EmitsOptions>( export function defineEmits<E extends EmitsOptions = EmitsOptions>(
emitOptions: E, emitOptions: E,
): EmitFn<E> ): EmitFn<E>
export function defineEmits< export function defineEmits<T extends ComponentTypeEmits>(): T extends (
T extends ((...args: any[]) => any) | Record<string, any[]>, ...args: any[]
>(): T extends (...args: any[]) => any ? T : ShortEmits<T> ) => any
? T
: ShortEmits<T>
// implementation // implementation
export function defineEmits() { export function defineEmits() {
if (__DEV__) { if (__DEV__) {
@ -146,6 +148,10 @@ export function defineEmits() {
return null as any return null as any
} }
export type ComponentTypeEmits =
| ((...args: any[]) => any)
| Record<string, any[]>
type RecordToUnion<T extends Record<string, any>> = T[keyof T] type RecordToUnion<T extends Record<string, any>> = T[keyof T]
type ShortEmits<T extends Record<string, any>> = UnionToIntersection< type ShortEmits<T extends Record<string, any>> = UnionToIntersection<
@ -191,15 +197,33 @@ export function defineOptions<
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
>( >(
options?: ComponentOptionsWithoutProps< options?: ComponentOptionsBase<
{}, {},
RawBindings, RawBindings,
D, D,
C, C,
M, M,
Mixin, Mixin,
Extends Extends,
> & { emits?: undefined; expose?: undefined; slots?: undefined }, {}
> & {
/**
* props should be defined via defineProps().
*/
props: never
/**
* emits should be defined via defineEmits().
*/
emits?: never
/**
* expose should be defined via defineExpose().
*/
expose?: never
/**
* slots should be defined via defineSlots().
*/
slots?: never
},
): void { ): void {
if (__DEV__) { if (__DEV__) {
warnRuntimeUsage(`defineOptions`) warnRuntimeUsage(`defineOptions`)

View File

@ -1,5 +1,6 @@
import { import {
EMPTY_OBJ, EMPTY_OBJ,
type OverloadParameters,
type UnionToIntersection, type UnionToIntersection,
camelize, camelize,
extend, extend,
@ -28,6 +29,7 @@ import {
compatModelEmit, compatModelEmit,
compatModelEventPrefix, compatModelEventPrefix,
} from './compat/componentVModel' } from './compat/componentVModel'
import type { ComponentTypeEmits } from './apiSetupHelpers'
export type ObjectEmitsOptions = Record< export type ObjectEmitsOptions = Record<
string, string,
@ -36,7 +38,8 @@ export type ObjectEmitsOptions = Record<
export type EmitsOptions = ObjectEmitsOptions | string[] export type EmitsOptions = ObjectEmitsOptions | string[]
export type EmitsToProps<T extends EmitsOptions> = T extends string[] export type EmitsToProps<T extends EmitsOptions | ComponentTypeEmits> =
T extends string[]
? { ? {
[K in `on${Capitalize<T[number]>}`]?: (...args: any[]) => any [K in `on${Capitalize<T[number]>}`]?: (...args: any[]) => any
} }
@ -54,6 +57,23 @@ export type EmitsToProps<T extends EmitsOptions> = T extends string[]
} }
: {} : {}
export type TypeEmitsToOptions<T extends ComponentTypeEmits> =
T extends Record<string, any[]>
? {
[K in keyof T]: T[K] extends [...args: infer Args]
? (...args: Args) => any
: () => any
}
: T extends (...args: any[]) => any
? ParametersToFns<OverloadParameters<T>>
: {}
type ParametersToFns<T extends any[]> = {
[K in T[0]]: K extends `${infer C}`
? (...args: T extends [C, ...infer Args] ? Args : never) => any
: never
}
export type ShortEmitsToObject<E> = export type ShortEmitsToObject<E> =
E extends Record<string, any[]> E extends Record<string, any[]>
? { ? {

View File

@ -54,7 +54,11 @@ import type {
ExtractDefaultPropTypes, ExtractDefaultPropTypes,
ExtractPropTypes, ExtractPropTypes,
} from './componentProps' } from './componentProps'
import type { EmitsOptions, EmitsToProps } from './componentEmits' import type {
EmitsOptions,
EmitsToProps,
TypeEmitsToOptions,
} from './componentEmits'
import type { Directive } from './directives' import type { Directive } from './directives'
import { import {
type ComponentPublicInstance, type ComponentPublicInstance,
@ -76,7 +80,10 @@ import {
import type { OptionMergeFunction } from './apiCreateApp' import type { OptionMergeFunction } from './apiCreateApp'
import { LifecycleHooks } from './enums' import { LifecycleHooks } from './enums'
import type { SlotsType } from './componentSlots' import type { SlotsType } from './componentSlots'
import { normalizePropsOrEmits } from './apiSetupHelpers' import {
type ComponentTypeEmits,
normalizePropsOrEmits,
} from './apiSetupHelpers'
/** /**
* Interface for declaring custom options. * Interface for declaring custom options.
@ -218,183 +225,6 @@ export interface RuntimeCompilerOptions {
delimiters?: [string, string] delimiters?: [string, string]
} }
export type ComponentOptionsWithoutProps<
Props = {},
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions,
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
PE = Props & EmitsToProps<E>,
> = ComponentOptionsBase<
PE,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
{},
I,
II,
S,
LC,
Directives,
Exposed,
Provide
> & {
props?: undefined
} & ThisType<
CreateComponentPublicInstance<
PE,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
PE,
{},
false,
I,
S,
LC,
Directives,
Exposed
>
>
export type ComponentOptionsWithArrayProps<
PropNames extends string = string,
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions,
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
Props = Prettify<Readonly<{ [key in PropNames]?: any } & EmitsToProps<E>>>,
> = ComponentOptionsBase<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
{},
I,
II,
S,
LC,
Directives,
Exposed,
Provide
> & {
props: PropNames[]
} & ThisType<
CreateComponentPublicInstance<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
Props,
{},
false,
I,
S,
LC,
Directives,
Exposed
>
>
export type ComponentOptionsWithObjectProps<
PropsOptions = ComponentObjectPropsOptions,
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions,
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
Props = Prettify<Readonly<ExtractPropTypes<PropsOptions> & EmitsToProps<E>>>,
Defaults = ExtractDefaultPropTypes<PropsOptions>,
> = ComponentOptionsBase<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
Defaults,
I,
II,
S,
LC,
Directives,
Exposed,
Provide
> & {
props: PropsOptions & ThisType<void>
} & ThisType<
CreateComponentPublicInstance<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
Props,
Defaults,
false,
I,
S,
LC,
Directives
>
>
export type ComponentOptions< export type ComponentOptions<
Props = {}, Props = {},
RawBindings = any, RawBindings = any,
@ -1238,3 +1068,203 @@ function mergeWatchOptions(
} }
return merged return merged
} }
// Deprecated legacy types, kept because they were previously exported ---------
/**
* @deprecated
*/
export type ComponentOptionsWithoutProps<
Props = {},
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = {},
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
TE extends ComponentTypeEmits = {},
ResolvedEmits extends EmitsOptions = {} extends E
? TypeEmitsToOptions<TE>
: E,
PE = Props & EmitsToProps<ResolvedEmits>,
> = ComponentOptionsBase<
PE,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
{},
I,
II,
S,
LC,
Directives,
Exposed,
Provide
> & {
props?: never
/**
* @private for language-tools use only
*/
__typeProps?: Props
/**
* @private for language-tools use only
*/
__typeEmits?: TE
} & ThisType<
CreateComponentPublicInstance<
PE,
RawBindings,
D,
C,
M,
Mixin,
Extends,
ResolvedEmits,
EE,
{},
false,
I,
S,
LC,
Directives,
Exposed
>
>
/**
* @deprecated
*/
export type ComponentOptionsWithArrayProps<
PropNames extends string = string,
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions,
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
Props = Prettify<Readonly<{ [key in PropNames]?: any } & EmitsToProps<E>>>,
> = ComponentOptionsBase<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
{},
I,
II,
S,
LC,
Directives,
Exposed,
Provide
> & {
props: PropNames[]
} & ThisType<
CreateComponentPublicInstance<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
Props,
{},
false,
I,
S,
LC,
Directives,
Exposed
>
>
/**
* @deprecated
*/
export type ComponentOptionsWithObjectProps<
PropsOptions = ComponentObjectPropsOptions,
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions,
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
Props = Prettify<Readonly<ExtractPropTypes<PropsOptions> & EmitsToProps<E>>>,
Defaults = ExtractDefaultPropTypes<PropsOptions>,
> = ComponentOptionsBase<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
Defaults,
I,
II,
S,
LC,
Directives,
Exposed,
Provide
> & {
props: PropsOptions & ThisType<void>
} & ThisType<
CreateComponentPublicInstance<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
Props,
Defaults,
false,
I,
S,
LC,
Directives
>
>

View File

@ -67,7 +67,7 @@ export interface PropOptions<T = any, D = T> {
skipFactory?: boolean skipFactory?: boolean
} }
export type PropType<T> = PropConstructor<T> | PropConstructor<T>[] export type PropType<T> = PropConstructor<T> | (PropConstructor<T> | null)[]
type PropConstructor<T = any> = type PropConstructor<T = any> =
| { new (...args: any[]): T & {} } | { new (...args: any[]): T & {} }
@ -107,8 +107,10 @@ type DefaultKeys<T> = {
: never : never
}[keyof T] }[keyof T]
type InferPropType<T> = [T] extends [null] type InferPropType<T, NullAsAny = true> = [T] extends [null]
? any // null & true would fail to infer ? NullAsAny extends true
? any
: null
: [T] extends [{ type: null | true }] : [T] extends [{ type: null | true }]
? any // As TS issue https://github.com/Microsoft/TypeScript/issues/14829 // somehow `ObjectConstructor` when inferred from { (): T } becomes `any` // `BooleanConstructor` when inferred from PropConstructor(with PropMethod) becomes `Boolean` ? any // As TS issue https://github.com/Microsoft/TypeScript/issues/14829 // somehow `ObjectConstructor` when inferred from { (): T } becomes `any` // `BooleanConstructor` when inferred from PropConstructor(with PropMethod) becomes `Boolean`
: [T] extends [ObjectConstructor | { type: ObjectConstructor }] : [T] extends [ObjectConstructor | { type: ObjectConstructor }]
@ -119,8 +121,8 @@ type InferPropType<T> = [T] extends [null]
? Date ? Date
: [T] extends [(infer U)[] | { type: (infer U)[] }] : [T] extends [(infer U)[] | { type: (infer U)[] }]
? U extends DateConstructor ? U extends DateConstructor
? Date | InferPropType<U> ? Date | InferPropType<U, false>
: InferPropType<U> : InferPropType<U, false>
: [T] extends [Prop<infer V, infer D>] : [T] extends [Prop<infer V, infer D>]
? unknown extends V ? unknown extends V
? IfAny<V, V, D> ? IfAny<V, V, D>
@ -594,7 +596,7 @@ function validatePropName(key: string) {
// use function string name to check type constructors // use function string name to check type constructors
// so that it works across vms / iframes. // so that it works across vms / iframes.
function getType(ctor: Prop<any>): string { function getType(ctor: Prop<any> | null): string {
// Early return for null to avoid unnecessary computations // Early return for null to avoid unnecessary computations
if (ctor === null) { if (ctor === null) {
return 'null' return 'null'
@ -614,7 +616,7 @@ function getType(ctor: Prop<any>): string {
return '' return ''
} }
function isSameType(a: Prop<any>, b: Prop<any>): boolean { function isSameType(a: Prop<any> | null, b: Prop<any> | null): boolean {
return getType(a) === getType(b) return getType(a) === getType(b)
} }
@ -707,24 +709,27 @@ type AssertionResult = {
/** /**
* dev only * dev only
*/ */
function assertType(value: unknown, type: PropConstructor): AssertionResult { function assertType(
value: unknown,
type: PropConstructor | null,
): AssertionResult {
let valid let valid
const expectedType = getType(type) const expectedType = getType(type)
if (isSimpleType(expectedType)) { if (expectedType === 'null') {
valid = value === null
} else if (isSimpleType(expectedType)) {
const t = typeof value const t = typeof value
valid = t === expectedType.toLowerCase() valid = t === expectedType.toLowerCase()
// for primitive wrapper objects // for primitive wrapper objects
if (!valid && t === 'object') { if (!valid && t === 'object') {
valid = value instanceof type valid = value instanceof (type as PropConstructor)
} }
} else if (expectedType === 'Object') { } else if (expectedType === 'Object') {
valid = isObject(value) valid = isObject(value)
} else if (expectedType === 'Array') { } else if (expectedType === 'Array') {
valid = isArray(value) valid = isArray(value)
} else if (expectedType === 'null') {
valid = value === null
} else { } else {
valid = value instanceof type valid = value instanceof (type as PropConstructor)
} }
return { return {
valid, valid,

View File

@ -76,6 +76,7 @@ export {
withDefaults, withDefaults,
type DefineProps, type DefineProps,
type ModelRef, type ModelRef,
type ComponentTypeEmits,
} from './apiSetupHelpers' } from './apiSetupHelpers'
/** /**
@ -260,9 +261,6 @@ export type {
export type { export type {
ComponentOptions, ComponentOptions,
ComponentOptionsMixin, ComponentOptionsMixin,
ComponentOptionsWithoutProps,
ComponentOptionsWithObjectProps,
ComponentOptionsWithArrayProps,
ComponentCustomOptions, ComponentCustomOptions,
ComponentOptionsBase, ComponentOptionsBase,
ComponentProvideOptions, ComponentProvideOptions,
@ -272,7 +270,11 @@ export type {
RuntimeCompilerOptions, RuntimeCompilerOptions,
ComponentInjectOptions, ComponentInjectOptions,
} from './componentOptions' } from './componentOptions'
export type { EmitsOptions, ObjectEmitsOptions } from './componentEmits' export type {
EmitsOptions,
ObjectEmitsOptions,
EmitsToProps,
} from './componentEmits'
export type { export type {
ComponentPublicInstance, ComponentPublicInstance,
ComponentCustomProperties, ComponentCustomProperties,

View File

@ -88,10 +88,14 @@ describe('defineCustomElement', () => {
describe('props', () => { describe('props', () => {
const E = defineCustomElement({ const E = defineCustomElement({
props: ['foo', 'bar', 'bazQux'], props: {
foo: [String, null],
bar: Object,
bazQux: null,
},
render() { render() {
return [ return [
h('div', null, this.foo), h('div', null, this.foo || ''),
h('div', null, this.bazQux || (this.bar && this.bar.x)), h('div', null, this.bazQux || (this.bar && this.bar.x)),
] ]
}, },

View File

@ -1,16 +1,19 @@
import { import {
type Component,
type ComponentInjectOptions, type ComponentInjectOptions,
type ComponentInternalInstance, type ComponentInternalInstance,
type ComponentObjectPropsOptions,
type ComponentOptions, type ComponentOptions,
type ComponentOptionsBase,
type ComponentOptionsMixin, type ComponentOptionsMixin,
type ComponentOptionsWithArrayProps, type ComponentProvideOptions,
type ComponentOptionsWithObjectProps,
type ComponentOptionsWithoutProps,
type ComponentPropsOptions,
type ComputedOptions, type ComputedOptions,
type ConcreteComponent, type ConcreteComponent,
type CreateComponentPublicInstance,
type DefineComponent, type DefineComponent,
type Directive,
type EmitsOptions, type EmitsOptions,
type EmitsToProps,
type ExtractPropTypes, type ExtractPropTypes,
type MethodOptions, type MethodOptions,
type RenderFunction, type RenderFunction,
@ -41,98 +44,79 @@ export function defineCustomElement<Props, RawBindings = object>(
) => RawBindings | RenderFunction, ) => RawBindings | RenderFunction,
): VueElementConstructor<Props> ): VueElementConstructor<Props>
// overload 2: object format with no props // overload 2: defineCustomElement with options object, infer props from options
export function defineCustomElement< export function defineCustomElement<
Props = {}, // props
RawBindings = {}, RuntimePropsOptions extends
D = {}, ComponentObjectPropsOptions = ComponentObjectPropsOptions,
C extends ComputedOptions = {}, PropsKeys extends string = string,
M extends MethodOptions = {}, // emits
RuntimeEmitsOptions extends EmitsOptions = {},
EmitsKeys extends string = string,
// other options
Data = {},
SetupBindings = {},
Computed extends ComputedOptions = {},
Methods extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions, InjectOptions extends ComponentInjectOptions = {},
EE extends string = string, InjectKeys extends string = string,
I extends ComponentInjectOptions = {}, Slots extends SlotsType = {},
II extends string = string, LocalComponents extends Record<string, Component> = {},
S extends SlotsType = {}, Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
// resolved types
InferredProps = string extends PropsKeys
? ComponentObjectPropsOptions extends RuntimePropsOptions
? {}
: ExtractPropTypes<RuntimePropsOptions>
: { [key in PropsKeys]?: any },
ResolvedProps = InferredProps & EmitsToProps<RuntimeEmitsOptions>,
>( >(
options: ComponentOptionsWithoutProps< options: {
Props, props?: (RuntimePropsOptions & ThisType<void>) | PropsKeys[]
RawBindings, } & ComponentOptionsBase<
D, ResolvedProps,
C, SetupBindings,
M, Data,
Computed,
Methods,
Mixin, Mixin,
Extends, Extends,
E, RuntimeEmitsOptions,
EE, EmitsKeys,
I, {}, // Defaults
II, InjectOptions,
S InjectKeys,
> & { styles?: string[] }, Slots,
): VueElementConstructor<Props> LocalComponents,
Directives,
// overload 3: object format with array props declaration Exposed,
export function defineCustomElement< Provide
PropNames extends string, > &
RawBindings, ThisType<
D, CreateComponentPublicInstance<
C extends ComputedOptions = {}, Readonly<ResolvedProps>,
M extends MethodOptions = {}, SetupBindings,
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Data,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Computed,
E extends EmitsOptions = Record<string, any>, Methods,
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
>(
options: ComponentOptionsWithArrayProps<
PropNames,
RawBindings,
D,
C,
M,
Mixin, Mixin,
Extends, Extends,
E, RuntimeEmitsOptions,
EE, EmitsKeys,
I, {},
II, false,
S InjectOptions,
> & { styles?: string[] }, Slots,
): VueElementConstructor<{ [K in PropNames]: any }> LocalComponents,
Directives,
// overload 4: object format with object props declaration Exposed
export function defineCustomElement< >
PropsOptions extends Readonly<ComponentPropsOptions>, >,
RawBindings, ): VueElementConstructor<ResolvedProps>
D,
C extends ComputedOptions = {},
M extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = Record<string, any>,
EE extends string = string,
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
>(
options: ComponentOptionsWithObjectProps<
PropsOptions,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
I,
II,
S
> & { styles?: string[] },
): VueElementConstructor<ExtractPropTypes<PropsOptions>>
// overload 5: defining a custom element from the returned value of // overload 5: defining a custom element from the returned value of
// `defineComponent` // `defineComponent`

View File

@ -21,3 +21,34 @@ export type Awaited<T> = T extends null | undefined
? Awaited<V> // recursively unwrap the value ? Awaited<V> // recursively unwrap the value
: never // the argument to `then` was not callable : never // the argument to `then` was not callable
: T // non-object or non-thenable : T // non-object or non-thenable
/**
* Utility for extracting the parameters from a function overload (for typed emits)
* https://github.com/microsoft/TypeScript/issues/32164#issuecomment-1146737709
*/
export type OverloadParameters<T extends (...args: any[]) => any> = Parameters<
OverloadUnion<T>
>
type OverloadProps<TOverload> = Pick<TOverload, keyof TOverload>
type OverloadUnionRecursive<
TOverload,
TPartialOverload = unknown,
> = TOverload extends (...args: infer TArgs) => infer TReturn
? TPartialOverload extends TOverload
? never
:
| OverloadUnionRecursive<
TPartialOverload & TOverload,
TPartialOverload &
((...args: TArgs) => TReturn) &
OverloadProps<TOverload>
>
| ((...args: TArgs) => TReturn)
: never
type OverloadUnion<TOverload extends (...args: any[]) => any> = Exclude<
OverloadUnionRecursive<(() => never) & TOverload>,
TOverload extends () => never ? never : () => never
>