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

Универсальные компоненты — это повторно используемые блоки интерфейса, которые можно применять в разных местах приложения с минимальными изменениями. Основная цель таких компонентов — снижение дублирования кода и упрощение поддержки проекта.

Создание универсального компонента

Для создания универсального компонента необходимо выделить обобщённую логику и шаблон, который может адаптироваться к различным сценариям через props, слоты и события.

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

<template>
  <button
    :class="['btn', typeClass, sizeClass]"
    :disabled="disabled"
    @click="$emit('click')"
  >
    <slot>Кнопка</slot>
  </button>
</template>

<script>
export default {
  name: 'BaseButton',
  props: {
    type: {
      type: String,
      default: 'primary',
    },
    size: {
      type: String,
      default: 'medium',
    },
    disabled: {
      type: Boolean,
      default: false,
    },
  },
  computed: {
    typeClass() {
      return `btn--${this.type}`;
    },
    sizeClass() {
      return `btn--${this.size}`;
    },
  },
};
</script>

<style scoped>
.btn {
  border: none;
  cursor: pointer;
}
.btn--primary { background-color: #42b983; color: white; }
.btn--secondary { background-color: #35495e; color: white; }
.btn--medium { padding: 8px 16px; font-size: 14px; }
.btn--large { padding: 12px 24px; font-size: 16px; }
</style>

Ключевые моменты:

  • Props позволяют задавать динамические свойства.
  • Слот даёт возможность вставлять произвольное содержимое внутрь компонента.
  • $emit передаёт события наружу, сохраняя компонент универсальным.

Использование слотов для гибкости

Слоты — основной инструмент для повышения универсальности компонентов. Vue поддерживает:

  • default slot — стандартный слот для основного содержимого.
  • named slots — именованные слоты для разных областей компонента.
  • scoped slots — слоты, которые передают данные от компонента к родителю.

Пример карточки с именованными слотами:

<template>
  <div class="card">
    <header class="card-header">
      <slot name="header"></slot>
    </header>
    <main class="card-body">
      <slot></slot>
    </main>
    <footer class="card-footer">
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

<script>
export default {
  name: 'Card',
};
</script>

<style scoped>
.card { border: 1px solid #ddd; border-radius: 4px; padding: 16px; }
.card-header, .card-footer { font-weight: bold; }
</style>

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

<Card>
  <template #header>Заголовок</template>
  Основное содержимое карточки
  <template #footer>Подвал</template>
</Card>

Динамические компоненты

Vue позволяет переключать компоненты в зависимости от состояния с помощью component и is:

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

<script>
import ComponentA from './ComponentA.vue';
import ComponentB from './ComponentB.vue';

export default {
  data() {
    return {
      currentComponent: 'ComponentA',
    };
  },
  components: { ComponentA, ComponentB },
};
</script>

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

Миксины и композиция

Для повторяющейся логики используют миксины или Composition API.

Пример с Composition API:

import { ref } from 'vue';

export function useToggle(initial = false) {
  const state = ref(initial);
  const toggle = () => state.value = !state.value;
  return { state, toggle };
}

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

<script setup>
import { useToggle } from './useToggle';

const { state: isOpen, toggle } = useToggle();
</script>

<template>
  <button @click="toggle">Переключить</button>
  <p v-if="isOpen">Содержимое открыто</p>
</template>

Передача данных через props и события

Универсальные компоненты должны быть чистыми, то есть не хранить лишнего состояния, а управляться через props и события. Это повышает их тестируемость и переиспользуемость.

Пример передачи данных:

<BaseInput v-model="username" placeholder="Введите имя" />

Где v-model автоматически связывает проп modelValue и событие update:modelValue:

props: { modelValue: String },
emits: ['update:modelValue'],

Обработка событий

Для универсальных компонентов важно делегировать события родителю, не связывая их с конкретной бизнес-логикой. Это достигается через $emit:

<button @click="$emit('submit')">Отправить</button>

Родитель решает, как обрабатывать событие:

<BaseButton @submit="handleSubmit" />

Практические рекомендации

  • Минимизировать внутреннее состояние, оставляя управление данными родителю.
  • Использовать слоты для кастомизации шаблона.
  • Разделять стили и логику, чтобы один компонент можно было легко адаптировать к разным темам.
  • Комбинировать Composition API и props для высокой гибкости.
  • Обрабатывать события наружу, не жестко привязывая их к конкретной логике.

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