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,
withModifiers,
} from 'vue'
import { type IsUnion, describe, expectType } from './utils'
import { type IsAny, type IsUnion, describe, expectType } from './utils'
describe('with object props', () => {
interface ExpectedProps {
@ -1623,3 +1623,146 @@ declare const MyButton: DefineComponent<
{}
>
;<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,
ComponentOptionsBase,
ComponentOptionsMixin,
ComponentOptionsWithArrayProps,
ComponentOptionsWithObjectProps,
ComponentOptionsWithoutProps,
ComponentProvideOptions,
ComputedOptions,
MethodOptions,
@ -25,7 +22,11 @@ import type {
ExtractDefaultPropTypes,
ExtractPropTypes,
} from './componentProps'
import type { EmitsOptions, EmitsToProps } from './componentEmits'
import type {
EmitsOptions,
EmitsToProps,
TypeEmitsToOptions,
} from './componentEmits'
import { extend, isFunction } from '@vue/shared'
import type { VNodeProps } from './vnode'
import type {
@ -34,6 +35,7 @@ import type {
} from './componentPublicInstance'
import type { SlotsType } from './componentSlots'
import type { Directive } from './directives'
import type { ComponentTypeEmits } from './apiSetupHelpers'
export type PublicProps = VNodeProps &
AllowedComponentProps &
@ -64,6 +66,7 @@ export type DefineComponent<
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
MakeDefaultsOptional extends boolean = true,
> = ComponentPublicInstanceConstructor<
CreateComponentPublicInstance<
Props,
@ -76,7 +79,7 @@ export type DefineComponent<
E,
PP & Props,
Defaults,
true,
MakeDefaultsOptional,
{},
S,
LC & GlobalComponents,
@ -169,183 +172,114 @@ export function defineComponent<
},
): DefineSetupFnComponent<Props, E, S>
// overload 2: object format with no props
// (uses user defined props interface)
// return type is for Vetur and TSX support
// overload 2: defineComponent with options object, infer props from options
export function defineComponent<
Props = {},
RawBindings = {},
D = {},
C extends ComputedOptions = {},
M extends MethodOptions = {},
// props
TypeProps,
RuntimePropsOptions extends
ComponentObjectPropsOptions = ComponentObjectPropsOptions,
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,
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> = {},
InjectOptions extends ComponentInjectOptions = {},
InjectKeys extends string = string,
Slots extends SlotsType = {},
LocalComponents extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
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<
Props,
RawBindings,
D,
C,
M,
options: {
props?: (RuntimePropsOptions & ThisType<void>) | RuntimePropsKeys[]
/**
* @private for language-tools use only
*/
__typeProps?: TypeProps
/**
* @private for language-tools use only
*/
__typeEmits?: TypeEmits
} & ComponentOptionsBase<
ResolvedProps,
SetupBindings,
Data,
Computed,
Methods,
Mixin,
Extends,
E,
EE,
I,
II,
S,
LC,
RuntimeEmitsOptions,
RuntimeEmitsKeys,
{}, // Defaults
InjectOptions,
InjectKeys,
Slots,
LocalComponents,
Directives,
Exposed,
Provide
>,
> &
ThisType<
CreateComponentPublicInstance<
ResolvedProps,
SetupBindings,
Data,
Computed,
Methods,
Mixin,
Extends,
ResolvedEmits,
RuntimeEmitsKeys,
{},
false,
InjectOptions,
Slots,
LocalComponents,
Directives,
Exposed
>
>,
): DefineComponent<
Props,
RawBindings,
D,
C,
M,
InferredProps,
SetupBindings,
Data,
Computed,
Methods,
Mixin,
Extends,
E,
EE,
ResolvedEmits,
RuntimeEmitsKeys,
PublicProps,
ResolveProps<Props, E>,
ExtractDefaultPropTypes<Props>,
S,
LC,
ResolvedProps,
ExtractDefaultPropTypes<RuntimePropsOptions>,
Slots,
LocalComponents,
Directives,
Exposed,
Provide
>
// overload 3: object format with array props declaration
// 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
Provide,
// MakeDefaultsOptional - if TypeProps is provided, set to false to use
// user props types verbatim
unknown extends TypeProps ? true : false
>
// implementation, close to no-op

View File

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

View File

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

View File

@ -54,7 +54,11 @@ import type {
ExtractDefaultPropTypes,
ExtractPropTypes,
} from './componentProps'
import type { EmitsOptions, EmitsToProps } from './componentEmits'
import type {
EmitsOptions,
EmitsToProps,
TypeEmitsToOptions,
} from './componentEmits'
import type { Directive } from './directives'
import {
type ComponentPublicInstance,
@ -76,7 +80,10 @@ import {
import type { OptionMergeFunction } from './apiCreateApp'
import { LifecycleHooks } from './enums'
import type { SlotsType } from './componentSlots'
import { normalizePropsOrEmits } from './apiSetupHelpers'
import {
type ComponentTypeEmits,
normalizePropsOrEmits,
} from './apiSetupHelpers'
/**
* Interface for declaring custom options.
@ -218,183 +225,6 @@ export interface RuntimeCompilerOptions {
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<
Props = {},
RawBindings = any,
@ -1238,3 +1068,203 @@ function mergeWatchOptions(
}
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
}
export type PropType<T> = PropConstructor<T> | PropConstructor<T>[]
export type PropType<T> = PropConstructor<T> | (PropConstructor<T> | null)[]
type PropConstructor<T = any> =
| { new (...args: any[]): T & {} }
@ -107,8 +107,10 @@ type DefaultKeys<T> = {
: never
}[keyof T]
type InferPropType<T> = [T] extends [null]
? any // null & true would fail to infer
type InferPropType<T, NullAsAny = true> = [T] extends [null]
? NullAsAny extends true
? any
: null
: [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`
: [T] extends [ObjectConstructor | { type: ObjectConstructor }]
@ -119,8 +121,8 @@ type InferPropType<T> = [T] extends [null]
? Date
: [T] extends [(infer U)[] | { type: (infer U)[] }]
? U extends DateConstructor
? Date | InferPropType<U>
: InferPropType<U>
? Date | InferPropType<U, false>
: InferPropType<U, false>
: [T] extends [Prop<infer V, infer D>]
? unknown extends V
? IfAny<V, V, D>
@ -594,7 +596,7 @@ function validatePropName(key: string) {
// use function string name to check type constructors
// 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
if (ctor === null) {
return 'null'
@ -614,7 +616,7 @@ function getType(ctor: Prop<any>): string {
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)
}
@ -707,24 +709,27 @@ type AssertionResult = {
/**
* dev only
*/
function assertType(value: unknown, type: PropConstructor): AssertionResult {
function assertType(
value: unknown,
type: PropConstructor | null,
): AssertionResult {
let valid
const expectedType = getType(type)
if (isSimpleType(expectedType)) {
if (expectedType === 'null') {
valid = value === null
} else if (isSimpleType(expectedType)) {
const t = typeof value
valid = t === expectedType.toLowerCase()
// for primitive wrapper objects
if (!valid && t === 'object') {
valid = value instanceof type
valid = value instanceof (type as PropConstructor)
}
} else if (expectedType === 'Object') {
valid = isObject(value)
} else if (expectedType === 'Array') {
valid = isArray(value)
} else if (expectedType === 'null') {
valid = value === null
} else {
valid = value instanceof type
valid = value instanceof (type as PropConstructor)
}
return {
valid,

View File

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

View File

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

View File

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

View File

@ -21,3 +21,34 @@ export type Awaited<T> = T extends null | undefined
? Awaited<V> // recursively unwrap the value
: never // the argument to `then` was not callable
: 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
>