Subscribe on changes!

Add types `Props<T>`, `Events<T>` and `Slots<T>` which provide types for components

avatar
Dec 1st 2022

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]
}
avatar
Jan 3rd 2023

This is awesome. Would be cool to have it as a native helper type.

avatar
Sep 30th 2023

Try vue-component-type-helpers

avatar
Nov 29th 2023

There's a PR to introduce these type helpers, if you have more type helpers you think might be useful, please let me know