вариант

vue-patterns

affaan-m/ECC affaan-m/ECC

В книге представлены шаблоны использования Composition API в Vue.js 3, архитектура компонентов, передовые практики реактивности, управление состоянием в Pinia, навигация с помощью Vue Router, а также шаблоны серверного рендеринга (SSR) в Nuxt для проектов на базе Vue, Nuxt, Vite или Pinia.

...Расширить все
0
Обновлено время 2 октября 2026 г.

Шаблоны и лучшие практики Vue.js

Подробное руководство по разработке на Vue.js 3 с использованием Composition API (

Презентационные и контейнерные компоненты

  • Контейнерные компоненты: собственное извлечение данных, состояние и побочные эффекты. Отображение презентационных компонентов.
  • Компоненты представления: получают пропсы, генерируют события. Никаких вызовов API, никакого доступа к хранилищу. Чистое рендеринг.

Рекомендации по использованию пропсов

// Пропсы на основе типов с значениями по умолчанию
interface Props {
  label: string;
  variant?: "primary" | "secondary";
  disabled?: boolean;
  items: Item[];
}

const props = withDefaults(defineProps(), {
  variant: "primary",
  disabled: false,
});
  • Всегда указывайте тип, а такжеобязательные значения и значения по умолчанию, где это уместно.
  • Булевые свойства: isXxx, hasXxx, canXxx.
  • Никогда не изменяйте свойства напрямую — вместо этого генерируйте события.
  • Для привязки v-model используйте defineModel() (Vue 3.4+) или modelValue + update:modelValue.

События

const emit = defineEmits<{
  submit: [];
  "update:modelValue": [value: string];
  select: [id: string, index: number];
}>();
  • В шаблонах используйте формат «kebab-case» (@update:model-value).
  • В скрипте используйте camelCase (emit("update:modelValue", val)).

3. Композиты (повторно используемая логика)

Структура

// composables/useDebounce.ts
export function useDebounce(value: MaybeRef, delay: number): Ref {
  const debounced = ref(toValue(value)) as Ref;

  let timer: ReturnType;
  watch(
    () => toValue(value),
    (newVal) => {
      clearTimeout(timer);
      timer = setTimeout(() => { debounced.value = newVal; }, delay);
    }
  );

  onUnmounted(() => clearTimeout(timer));
  return readonly(debounced);
}

Правила

  • Должно начинаться с префикса use.
  • Возвращать реактивные значения (ref, computed, reactive), никогда — простые примитивы.
  • Принимать реактивные входные данные через MaybeRef / toRef() / toValue().
  • Убирать побочные эффекты в onUnmounted или в методе onCleanup наблюдателя.
  • Никаких побочных эффектов в области видимости модуля.

vs. микшины

Композиты полностью заменяют миксины Vue 2:

  • Миксины: непрозрачный поток данных, конфликты «источников истины», конфликты имен.
  • Композитабелы: явные импорты, чёткие возвращаемые значения, возможность компоновки и удаления из кода.

4. Управление состоянием

Когда что использовать

Шаблон Пример использования
ref() / reactive() Состояние локального компонента
Пропсы + эмиссии Взаимодействие родитель-дочерний компонент
Предоставление / инъекция API тем, конфигурации и плагинов
Хранилище Pinia Глобальное, общее, сложное состояние
Композируемое состояние сервера API данных с кэшированием (обертка fetch/TanStack Query)

Настройка хранилища Pinia (рекомендуется)

// stores/useCartStore.ts
export const useCartStore = defineStore("cart", () => {
  const items = ref([]);
  const isLoading = ref(false);

  const totalPrice = computed(() =>
    items.value.reduce((sum, i) => sum + i.price * i.quantity, 0)
  );
  const itemCount = computed(() =>
    items.value.reduce((sum, i) => sum + i.quantity, 0)
  );

  async function addItem(productId: string) {
    isLoading.value = true;
    try {
      const item = await fetchProduct(productId);
      const existing = items.value.find(i => i.id === item.id);
      if (existing) existing.quantity++;
      else items.value.push({ ...item, quantity: 1 });
    } finally {
      isLoading.value = false;
    }
  }

  return { items, isLoading, totalPrice, itemCount, addItem };
});
  • Используйте синтаксис Setup Store (а не Options Store).
  • Предпочтительно используйте действия для изменений на бизнес-уровне и $patch() для групповых обновлений.
  • Каждое асинхронное действие: обработка загрузки + успешного выполнения + ошибки.

5. Vue Router

Определения маршрутов

const routes = [
  {
    path: "/users/:id",
    name: "user-detail",
    component: () => import("@/pages/UserDetail.vue"), // отложенная загрузка
    props: true, // передача параметров в качестве пропсов
    meta: { requiresAuth: true },
  },
];

Навигационные охранники

router.beforeEach((to, from) => {
  const { isLoggedIn } = useAuthStore();
  if (to.meta.requiresAuth && !isLoggedIn) {
    return { name: "login", query: { redirect: to.fullPath } };
  }
});

Реактивные параметры маршрута

Когда компонент остается смонтированным, но параметры маршрута изменяются:

const route = useRoute();
const id = computed(() => route.params.id as string);
watch(id, (newId) => fetchItem(newId));

6. Шаблоны шаблонов

Синтаксис шаблонов


Загрузка...
Ошибка: {{ error }}
{{ content }}
Содержимое, отображаемое по нажатию
{{ item.name }}
{{ item.name }}

7. Производительность

Техника Когда использовать
v-memo Элементы списка, которые редко меняются
v-once Контент, отображаемый один раз и остающийся статичным навсегда
shallowRef() Крупные структуры данных заменяются целиком
shallowReactive() Реактивны только свойства верхнего уровня
v-show вместо v-if Частое переключение видимости
Кэширование переключаемых представлений
Отложенные маршруты () => import(...) для некритических маршрутов
Suspense Асинхронная загрузка компонентов с резервным вариантом

8. Тестирование

Stack

  • Vitest для модульных тестов и тестов компонентов
  • Vue Test Utils для монтирования и взаимодействия
  • @pinia/testing для имитации хранилища
  • Playwright для E2E

Шаблон тестирования компонентов

import { mount } from "@vue/test-utils";
import { createPinia, setActivePinia } from "pinia";
import UserCard from "./UserCard.vue";

beforeEach(() => { setActivePinia(createPinia()); });

it("отображает и генерирует события", async () => {
  const wrapper = mount(UserCard, {
    props: { user: { id: "1", name: "Alice" } },
  });
  expect(wrapper.text()).toContain("Alice");
  await wrapper.find("button").trigger("click");
  expect(wrapper.emitted("select")![0]).toEqual(["1"]);
});

9. Шаблоны, специфичные для Nuxt

Автоимпорт

Nuxt автоматически импортирует ref, computed, watch, useFetch, useAsyncData и т. д. Используйте их напрямую, без импорта. В проектах, не относящихся к Nuxt, всегда импортируйте их явно.

useAsyncData / useFetch

const { data: user, pending, error, refresh } = await useAsyncData(
  "user", // уникальный ключ для кэширования
  () => $fetch(`/api/users/${id}`),
);

const { data: posts } = await useFetch("/api/posts", {
  query: { page: 1 },
  key: "posts-page-1", // предотвращает дублирование запросов
});

Маршруты сервера

// server/api/users/[id].ts
export default defineEventHandler(async (event) => {
  const { id } = await getValidatedRouterParams(event, z.object({
    id: z.string().uuid(),
  }).parse);
  // ... запрос и возврат данных
});

Конфигурация на время выполнения

// nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    // только для сервера
    apiSecret: "",
    // общедоступные (предоставляемые клиенту)
    public: {
      apiBase: "https://api.example.com",
    },
  },
});

10. Новые API в Vue 3.5+

Деструктуризация реактивных пропсов

В Vue 3.5 стабилизирована деструктурация реактивных пропсов — переменные, полученные путем деструктурирования из defineProps(), автоматически становятся реактивными:

// Vue 3.5+: деструктурированные пропсы являются реактивными (toRefs не требуется)
const { count = 0, msg = "hello" } = defineProps<{
  count?: number;
  msg?: string;
}>();

// Ограничение: нельзя отслеживать деструктурированный проп напрямую
watch(() => count, (newVal) => { ... }); // ПРОХОДИТ, требуется геттер

useTemplateRef()

Замените простые рефы с совпадающими именами на useTemplateRef() для ссылок в шаблонах:

import { useTemplateRef } from "vue";
const inputEl = useTemplateRef("input");
// "input" соответствует атрибуту ref="input" в шаблоне, а не имени переменной

Поддерживает динамические идентификаторы реф: useTemplateRef(dynamicRefId).

onWatcherCleanup()

API очистки наблюдателей, доступное для глобального импорта (Vue 3.5+). Его необходимо вызывать синхронно внутри обратного вызова наблюдателя:

import { watch, onWatcherCleanup } from "vue";

watch(userId, async (newId) => {
  const controller = new AbortController();
  onWatcherCleanup(() => controller.abort());
  // ... запрос с сигналом
});

useId()

Генерация уникальных идентификаторов, устойчивых при серверном рендеринге (SSR), для элементов форм и обеспечения доступности:

import { useId } from "vue";
const id = useId();

defer Teleport

позволяет телепортироваться к целям, отображаемым в том же цикле:

Контент

Отложенная гидратация (SSR)

defineAsyncComponent() теперь поддерживает стратегию hydrate:

import { defineAsyncComponent, hydrateOnVisible } from "vue";
const AsyncComp = defineAsyncComponent({
  loader: () => import("./Comp.vue"),
  hydrate: hydrateOnVisible(),
});

Антипаттерны

Антипаттерн Почему это неправильно Исправление
Деструктуризация defineProps() (Vue < 3.5) Создаёт моментальный снимок, теряет реактивность Доступ через props.xxx или использование toRefs()
watch() на деструктурированном пропе (Vue 3.5+) Ошибка компиляции — деструктурированные props нельзя отслеживать напрямую Используйте обёртку геттера: watch(() => count, ...)
v-if + v-for на одном и том же элементе Неоднозначный порядок выполнения Используйте вычисляемый отфильтрованный массив
v-for key = index Сбой состояния при изменении порядка Использование стабильных идентификаторов базы данных
Изменяющиеся свойства Нарушение однонаправленности потока данных Генерируйте события или используйте v-model
v-html с пользовательским контентом Уязвимость XSS Очистка с помощью DOMPurify
Миксины в Vue 3 Непрозрачные, склонные к конфликтам Заменить на композибелы
Побочные эффекты в области видимости модуля в композибелах Общие для всех экземпляров Область действия в onMounted + onUnmounted
reactive() для заменяемого состояния Замена нарушает реактивность Вместо этого используйте ref()
Наблюдатель без очистки Утечки памяти, условия гонки Используйте onCleanup или onWatcherCleanup() (Vue 3.5+)
API опций в новом коде Vue 3 Переход экосистемы на Composition API Используйте

Все файлы

1 файлов

Установить vue-patterns

Скачайте файлы навыков и распакуйте их в каталог .claude/skills/.

Скачать ZIP

Клонируйте репозиторий и скопируйте файлы навыка в свой проект.

git clone https://github.com/affaan-m/ECC/tree/main/skills/vue-patterns # Copy SKILL.md to your .claude/skills/ directory

Копировать Копировать
Быстрая настройка: Скопируйте папку со скиллом в каталог .claude/skills/ Claude автоматически обнаружит и запустит этот скилл
Репозиторий affaan-m/ECC

Похожие навыки

github-code-search
Обновлено время 29 июня 2026 г.
drizzle-orm
Обновлено время 29 июня 2026 г.
clickhouse-io
Обновлено время 29 июня 2026 г.
prisma-client-api
Обновлено время 29 июня 2026 г.