Generic компоненты

Generic компоненты — это компоненты, способные работать с различными типами данных или структур без изменения внутренней логики. Их основное назначение — повышение переиспользуемости кода и снижение дублирования. В Vue.js такой подход реализуется через слоты, пропсы с типами и композицию компонентов.


Использование слотов для универсальности

Слоты позволяют вставлять произвольный контент в компонент. Стандартный слот обеспечивает максимальную гибкость, а именованные слоты и слот v-slot добавляют возможность детальной настройки.

<template>
  <div class="card">
    <header>
      <slot name="header">Заголовок по умолчанию</slot>
    </header>
    <main>
      <slot>Контент по умолчанию</slot>
    </main>
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

<script>
export default {
  name: 'CardComponent'
}
</script>

Такой компонент может использоваться с любым содержимым, превращаясь в generic-структуру для карточек.

<CardComponent>
  <template #header>
    <h1>Новости</h1>
  </template>
  <p>Основной контент карточки.</p>
  <template #footer>
    <small>Источник: Vue.js Docs</small>
  </template>
</CardComponent>

Пропсы с типами и универсальные компоненты

Generic компоненты часто используют обобщенные пропсы для передачи данных с различной структурой. Vue 3 поддерживает строгую типизацию пропсов через TypeScript или через объект props с указанием типа.

<script lang="ts">
import { defineComponent, PropType } from 'vue'

interface Item {
  id: number
  name: string
}

export default defineComponent({
  name: 'ListComponent',
  props: {
    items: {
      type: Array as PropType<Item[]>,
      required: true
    }
  }
})
</script>

В шаблоне можно динамически рендерить элементы любого типа, используя v-for:

<template>
  <ul>
    <li v-for="item in items" :key="item.id">
      {{ item.name }}
    </li>
  </ul>
</template>

Преимущество такого подхода — компонент становится независимым от конкретного набора данных и может работать с любыми массивами объектов, соответствующими интерфейсу.


Передача функций и рендер-пропсы

Generic компонент может принимать рендер-функцию или метод для кастомного рендера каждого элемента. Такой паттерн называют render prop.

<template>
  <ul>
    <li v-for="item in items" :key="item.id">
      <slot :item="item">{{ item.name }}</slot>
    </li>
  </ul>
</template>

<script lang="ts">
import { defineComponent, PropType } from 'vue'

interface Item {
  id: number
  name: string
}

export default defineComponent({
  name: 'CustomList',
  props: {
    items: {
      type: Array as PropType<Item[]>,
      required: true
    }
  }
})
</script>

Использование компонента с кастомным рендером:

<CustomList :items="users">
  <template #default="{ item }">
    <strong>{{ item.name }}</strong> (ID: {{ item.id }})
  </template>
</CustomList>

Такой подход делает компонент полностью generic и расширяемым без изменения его внутреннего кода.


Dynamic компоненты и is

Для построения generic интерфейсов с различными типами визуальных компонентов используют динамические компоненты с атрибутом :is.

<component :is="currentComponent" :data="data" />

Здесь currentComponent может быть любым зарегистрированным компонентом, а data передается одинаковым образом. Это позволяет строить универсальные контейнеры, способные рендерить различные типы контента.


Комбинация generic компонентов и Composition API

Composition API усиливает generic подход, предоставляя возможность вынести повторяющуюся логику в composable функции. Generic компонент может использовать такие функции для работы с данными любого типа:

import { ref } from 'vue'

export function useGenericList<T>(initialItems: T[]) {
  const items = ref<T[]>(initialItems)
  const addItem = (item: T) => items.value.push(item)
  return { items, addItem }
}

Использование в компоненте:

import { defineComponent } from 'vue'
import { useGenericList } from './useGenericList'

export default defineComponent({
  setup() {
    const { items, addItem } = useGenericList<{ id: number; name: string }>([])
    return { items, addItem }
  }
})

Такой компонент становится максимально абстрактным и пригодным для любых типов данных, соответствующих ожидаемой структуре.


Generic компоненты в Vue.js обеспечивают высокий уровень переиспользуемости и гибкости. Слоты, типизированные пропсы, рендер-пропсы, динамические компоненты и Composition API — ключевые инструменты для построения абстрактных, независимых компонентов, способных работать с разнообразными данными и визуальными шаблонами.