Add types `Props<T>`, `Events<T>` and `Slots<T>` which provide types for components
What problem does this feature solve?
In order to enhance Typescript support, I would like to easily be able to extract the props, event and slots which a component supports. This feature would be very useful for creating better developer tools.
What does the proposed API look like?
Consider the following component:
<script setup lang="ts">
defineEmits<{
(event: 'change', value: string): void
}>()
withDefaults(
defineProps<{
name: string,
disabled: boolean,
}>(),
{
disabled: false,
}
)
</script>
<template>
<div>
<slot name="heading" />
</div>
<div>
<slot />
</div>
</template>
The proposed API would allow developers to do the following:
import MyComponent from './MyComponent.vue'
type P = Props<typeof MyComponent> // { name: string, disabled: boolean? }
type E = Events<typeof MyComponent> // { (event: 'change', value: string): void }
type S = Slots<typeof MyComponent> // 'default' | 'heading'
Currently, I am using the following definitions for Props<T>
, Events<T>
and Slots<T>
but I think they are fragile and could be made better.
import { ComponentOptions, ComponentOptionsMixin, ComputedOptions, InjectionKey, MethodOptions } from 'vue'
/** evaluates to `true` if `X` and `Y` are the same type, otherwise evaluates to `false` */
type Equals<X, Y> = (<T>() => T extends X ? 0 : 1) extends <T>() => T extends Y ? 0 : 1 ? true : false
/** extracts keys from `T` which are `readonly` */
type ReadonlyKeys<T> = {
[Ro in {
[K in keyof T]: Equals<Pick<T, K>, Readonly<Pick<T, K>>> extends true ? K : never
}[keyof T]]: T[Ro]
}
export type Renderable = ComponentOptions<any> & (abstract new (...args: any) => any)
export type Props<T extends Renderable> = Pick<InstanceType<T>['$props'], _PropKeys<T>>
type _PropKeys<T extends Renderable> = keyof (T extends ComponentOptions<infer P> ? ReadonlyKeys<P> : never)
export type Events<T extends Renderable> = T extends ComponentOptions<
never,
unknown,
unknown,
ComputedOptions,
MethodOptions,
ComponentOptionsMixin,
ComponentOptionsMixin,
infer E
>
? keyof E
: never
export type Slots<T extends Renderable> = _Slots<InstanceType<T>['$slots']>
type _Slots<S> = keyof {
[K in keyof S as S[K] extends CallableFunction ? K : never]: S[K]
}