typescriptvue.jsvuejs3vuetify.js

Tuple type inference in Vue.js named slots with Typescript


Vue.js has a feature called name slots which can be used to override whatever gets rendered by default for a certain slot:

<td v-for="(column, index) in tuple">
  <slot
    :name="slotName(column)" // string
    :value="getValue(item, column) as TupleValues<typeof tuple, typeof item>[index]

This is how it gets used:

<template #score="{ value }">
  // value should be TupleMap<typeof tuple, typeof item>['score']
  // score is the slot name previously returned by slotName(column)
</template>

This is a pattern used to customize whatever gets rendered by default for a certain slot.

columns is a tuple which defines each column of the table and which key should be used to get their value from the items of an array (the tuple can also define a function that returns a custom value for the column). Each item of the array represents a row.

I can easily get the value type from each tuple element with these types:

type TupleValues<
  T extends Array<{ key: keyof U | string; value?: keyof U | string | ((...args: any[]) => any) }>,
  U
> = {
  [P in keyof T]: T[P]['key'] extends keyof U
    ? U[T[P]['key']]
    : T[P]['value'] extends keyof U
      ? U[T[P]['value']]
      : T[P]['value'] extends (...args: unknown[]) => unknown
        ? ReturnType<T[P]['value']>
        : never
}

and

type TupleMap<
  T extends Array<{ key: keyof U | string; value?: keyof U | string | ((...args: any[]) => any) }>,
  U
> = {
  [P in T[number] as P['key']]: P['key'] extends keyof U
    ? U[P['key']]
    : P['value'] extends keyof U
      ? U[P['value']]
      : P['value'] extends (...args: unknown[]) => unknown
        ? ReturnType<P['value']>
        : never
}

But unfortunately the v-for loop makes it pointless.

Instead of using v-for I could manually define a slot for each tuple index, but that won't scale to any arbitrary column number:

<td>
  <slot
    :name="columnName(columns[0])" // I can infer the exact slot name from the tuple
    :value="getValue(item, column) as TupleValues<typeof tuple, typeof item>[0] // I can infer the exact slot value form the tuple
  > <DefaultSlotRenderer /> </slot>
</td>
<td>
  <slot
    :name="columnName(columns[1])" // I can infer the exact slot name from the tuple
    :value="getValue(item, column) as TupleValues<typeof tuple, typeof item>[1] // I can infer the exact slot value form the tuple
  > <DefaultSlotRenderer /> </slot>
</td>
<td>
  <slot
    :name="columnName(columns[2])" // I can infer the exact slot name from the tuple
    :value="getValue(item, column) as TupleValues<typeof tuple, typeof item>[2] // I can infer the exact slot value form the tuple
  > <DefaultSlotRenderer /> </slot>
</td>

EDIT: turns out it doesn't work even without the v-for loop and it's not the typings fault.

Playground

You should copy paste it locally because playground's intellisense is not the best.


Solution

  • The answer was defineSlots

    Updated playground