Компоненти на кшталт таблиці, списку з вибором чи автодоповнення працюють з будь-яким типом елементів. Без узагальнень доводиться писати items: any[], і батько втрачає перевірку типів у слотах і подіях.
Атрибут generic на <script setup>:
<!-- DataTable.vue -->
<script setup lang="ts" generic="T extends { id: number | string }">
defineProps<{
rows: T[]
columns: Array<{ key: keyof T; label: string }>
}>()
const emit = defineEmits<{
select: [row: T]
}>()
</script>
<template>
<table>
<tr v-for="row in rows" :key="row.id" @click="emit('select', row)">
<td v-for="column in columns" :key="String(column.key)">
<slot :name="String(column.key)" :row="row">{{ row[column.key] }}</slot>
</td>
</tr>
</table>
</template>
Що отримує батько:
<DataTable
:rows="orders"
:columns="[{ key: 'total', label: 'Сума' }]"
@select="(order) => open(order.number)"
/>
T виводиться з переданого rows як Order: key: 'totla' - помилка, у @select параметр має тип Order, а в слоті row - теж Order.
Можливості:
- кілька параметрів і обмеження:
generic="T extends Item, K extends keyof T"; - імпортовані типи в обмеженнях;
- типізовані слоти через
defineSlots<{ default(props: { item: T }): any }>().
Обмеження й нюанси:
- runtime не знає про
T- узагальнення існують лише для TypeScript. Vue перевіряє props за базовим типом (Array), а не заT; - виведення типу йде з props. Якщо
Tне можна вивести з жодного prop, він стає обмеженням (unknownчи тип зextends); - перевірку в шаблоні батька дає лише
vue-tsc/ Vue - Official; без них узагальнення - просто документація; - ref на узагальнений компонент типізується складніше - звичайний
InstanceType<typeof DataTable>не працює, бо компонент - функція з параметром типу. Для таких випадків використовуютьComponentExposedз пакетаvue-component-type-helpers.
Коли не варто: якщо компонент завжди працює з одним типом - узагальнення лише ускладнюють читання.