Cómo tipar scoped slots con defineSlots

Cómo usar TypeScript con scoped slots gracias a la nueva macro defineSlots de Vue 3.3>


Uno de los conceptos más abstractos de Vue.js son los slots con ámbito, también llamados scoped slots.

Si a esto le añadimos las complejidades de añadir un sistema de tipado como TypeScript, la dificultad puede aumentar.

Sin embargo, con la macro defineSlots de Vue 3.3>, añadir tipos a tus slots es más sencillo que nunca. Deja que te muestre como.

Usando defineSlots

El objetivo de la macro es mejorar la DX añadiendo sugerencias y validación de tipo en tu editor de código/IDE. defineSlots acepta un tipo literal como parámetro cuya propiedad es el nombre del slot y, como valor, una función que define las propiedades que el slot acepta.

Por ejemplo, imagina que tu slot actions ofrece acceso a una función llamada open:

TS
defineSlots<{
  actions(props: { open: () => void }): any; // Return type is currently ignored by the macro
}>();

    

Además, como extra, defineSlots retorna el objeto slots que obtendrías llamando a la macro useSlots.

Ejemplo práctico con defineSlots

Supongamos en tu componente AppProducts quieres ofrecer información de productos y quieres asignar sus tipos.

TS
interface Product {
    id: number
    name: string;
    price: number;
}

const slots = defineSlots<{
    default(props: {
        highlight: { // You can use literal types
            name: string;
            price: number;
        }
    }): void
    list(props: {
        products: Product[];
    }): void
}>()

    

Como ves, estamos declarando los tipos de dos slots:

  1. En el slot default esperamos un producto highlight con propiedades name y price.
  2. Para el slot list enviamos una lista de Product.

Esto, tan sencillo, tiene un profundo efecto. Por una parte estamos validando de forma adecuada los tilos tipos el slot en el componente AppProducts en sí. Por otro lado, TypeScript inferirá los tipos del slot en el componente “padre” que haga uso de los slots.

Vue
<template>
    <AppProduct>
      <template #default="{ highlight }">
        <h2>{{ highlight.name }} - ${{ highlight.price }}</h2>
      </template>

      <template #list="{ products }">
        <ul>
          <li v-for="product in products" :key="product.id">
            {{ product.name }} - ${{ product.price }}
          </li>
        </ul>
      </template>
    </AppProduct>
</template>

    

Demo

Como siempre, te dejo una demo.

Juan Andrés Núñez
Juan Andrés Núñez
Ingeniero Frontend Senior. Especialista en Vue.js. Speaker. Docente profesional. Estoico.