vue-patterns
affaan-m/ECC
В книге представлены шаблоны использования Composition API в Vue.js 3, архитектура компонентов, передовые практики реактивности, управление состоянием в Pinia, навигация с помощью Vue Router, а также шаблоны серверного рендеринга (SSR) в Nuxt для проектов на базе Vue, Nuxt, Vite или Pinia.
...Расширить всеШаблоны и лучшие практики Vue.js
Подробное руководство по разработке на Vue.js 3 с использованием Composition API (), охватывающее проектирование компонентов, реактивность, управление состоянием, маршрутизацию, тестирование и шаблоны SSR. Также включены рекомендации, специфичные для Nuxt, в тех случаях, когда они отличаются от стандартного Vue.
Когда активировать
Активируйте этот навык, если:
- В проекте используется Vue.js (любая версия), Nuxt, Vite + Vue или Pinia.
- Пользователь задаёт вопросы об архитектуре компонентов Vue, композибелах, реактивности или управлении состоянием.
- При рассмотрении однофайловых компонентов Vue (файлы
.vue). - Настраиваете Vue Router, хранилища Pinia или конфигурацию Vite/Vitest.
- Обсуждаете вопросы производительности, безопасности или шаблонов SSR, характерные для Vue.
1. Структура проекта
Рекомендуемая структура (подход «Feature-First»)
src/
├── api/ # Определения API-клиента и конечных точек
├── assets/ # Статические ресурсы (изображения, шрифты, иконки)
├── components/ # Общие/повторно используемые компоненты
│ ├── base/ # Базовые элементы пользовательского интерфейса (Button, Input, Modal)
│ └── features/ # Общие компоненты для конкретных функций
├── composables/ # Многократно используемая логика Composition API
├── layouts/ # Макеты страниц (необязательно)
├── pages/ # Компоненты страниц на уровне маршрутов
├── router/ # Конфигурация Vue Router
├── stores/ # Хранилища Pinia
├── types/ # Определения типов TypeScript
├── utils/ # Чистые утилитарные функции
└── App.vue # Корневой компонент
Имена файлов
| Условия | Когда использовать |
|---|---|
PascalCase.vue |
Все компоненты (обеспечивается модулем vue/multi-word-component-names) |
useCamelCase.ts |
Композитабельные компоненты |
camelCase.ts |
Утилиты, API-клиенты, типы |
Каталоги с именами в формате«кебаб-кейс» |
Сегменты маршрутов, папки с функциями |
2. Архитектура компонентов
Порядок компонентов в одном файле
Презентационные и контейнерные компоненты
- Контейнерные компоненты: собственное извлечение данных, состояние и побочные эффекты. Отображение презентационных компонентов.
- Компоненты представления: получают пропсы, генерируют события. Никаких вызовов 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 | Используйте |
| простых ref для ссылок в шаблонах | Отсутствует поддержка динамических ref, ненадёжное сопоставление имён | Используйте useTemplateRef() (Vue 3.5+) |
Связанные навыки
доступность— ARIA, семантический HTML, управление фокусомfrontend-patterns— межфреймворковая архитектура фронтендаTypeScript— лучшие практики TypeScript, применяемые в проектах Vuecoding-standards— Общие стандарты качества кода
---
name: vue-patterns
description: Provides Vue.js 3 Composition API patterns, component architecture, reactivity best practices, Pinia state management, Vue Router navigation, and Nuxt SSR patterns for Vue, Nuxt, Vite, or Pinia projects.
---
# Vue.js Patterns and Best Practices
Comprehensive guide for Vue.js 3 development using Composition API (`<script setup>`), covering component design, reactivity, state management, routing, testing, and SSR patterns. Nuxt-specific guidance is included where it differs from vanilla Vue.
## When to Activate
Activate this skill when:
- The project uses Vue.js (any version), Nuxt, Vite + Vue, or Pinia.
- The user asks about Vue component architecture, composables, reactivity, or state management.
- Reviewing Vue Single-File Components (`.vue` files).
- Setting up Vue Router, Pinia stores, or Vite/Vitest configuration.
- Discussing Vue-specific performance, security, or SSR patterns.
---
## 1. Project Structure
### Recommended Layout (Feature-First)
```
src/
├── api/ # API client and endpoint definitions
├── assets/ # Static assets (images, fonts, icons)
├── components/ # Shared/reusable components
│ ├── base/ # Base UI primitives (Button, Input, Modal)
│ └── features/ # Feature-specific shared components
├── composables/ # Reusable Composition API logic
├── layouts/ # Page layouts (optional)
├── pages/ # Route-level page components
├── router/ # Vue Router configuration
├── stores/ # Pinia stores
├── types/ # TypeScript type definitions
├── utils/ # Pure utility functions
└── App.vue # Root component
```
### File Naming
| Convention | When to Use |
|-----------|-------------|
| `PascalCase.vue` | All components (enforced by `vue/multi-word-component-names`) |
| `useCamelCase.ts` | Composables |
| `camelCase.ts` | Utilities, API clients, types |
| `kebab-case` directories | Route segments, feature folders |
---
## 2. Component Architecture
### Single-File Component Order
```vue
<script setup lang="ts">
// 1. Imports (vue → ecosystem → absolute → relative)
// 2. Props & Emits & Slots
// 3. Composables
// 4. Local state (ref/reactive)
// 5. Computed properties
// 6. Methods
// 7. Watchers
// 8. Lifecycle hooks
</script>
<template>
<!-- Template content -->
</template>
<style scoped>
/* Scoped styles */
</style>
```
### Presentational vs Container
- **Container components**: Own data fetching, state, and side effects. Render presentational components.
- **Presentational components**: Receive props, emit events. No API calls, no store access. Pure rendering.
### Props Best Practices
```ts
// Type-based props with defaults
interface Props {
label: string;
variant?: "primary" | "secondary";
disabled?: boolean;
items: Item[];
}
const props = withDefaults(defineProps<Props>(), {
variant: "primary",
disabled: false,
});
```
- Always provide `type`, and `required`/`default` where appropriate.
- Boolean props: `isXxx`, `hasXxx`, `canXxx`.
- Never mutate props — emit events instead.
- For v-model binding, use `defineModel()` (Vue 3.4+) or `modelValue` + `update:modelValue`.
### Events
```ts
const emit = defineEmits<{
submit: [];
"update:modelValue": [value: string];
select: [id: string, index: number];
}>();
```
- Use kebab-case in templates (`@update:model-value`).
- Use camelCase in script (`emit("update:modelValue", val)`).
---
## 3. Composables (Reusable Logic)
### Structure
```ts
// composables/useDebounce.ts
export function useDebounce<T>(value: MaybeRef<T>, delay: number): Ref<T> {
const debounced = ref(toValue(value)) as Ref<T>;
let timer: ReturnType<typeof setTimeout>;
watch(
() => toValue(value),
(newVal) => {
clearTimeout(timer);
timer = setTimeout(() => { debounced.value = newVal; }, delay);
}
);
onUnmounted(() => clearTimeout(timer));
return readonly(debounced);
}
```
### Rules
- Must start with `use` prefix.
- Return reactive values (`ref`, `computed`, `reactive`), never plain primitives.
- Accept reactive inputs via `MaybeRef` / `toRef()` / `toValue()`.
- Clean up side effects in `onUnmounted` or watcher `onCleanup`.
- No module-scope side effects.
### vs Mixins
Composables replace Vue 2 mixins entirely:
- **Mixins**: Opaque data flow, source-of-truth collisions, name conflicts.
- **Composables**: Explicit imports, clear return values, composable and tree-shakable.
---
## 4. State Management
### When to Use What
| Pattern | Use Case |
|---------|----------|
| `ref()` / `reactive()` | Local component state |
| Props + Emits | Parent-child communication |
| Provide / Inject | Theme, config, plugin API |
| Pinia store | Global, shared, complex state |
| Server state composable | API data with caching (wrap `fetch`/TanStack Query) |
### Pinia Setup Store (Preferred)
```ts
// stores/useCartStore.ts
export const useCartStore = defineStore("cart", () => {
const items = ref<CartItem[]>([]);
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 };
});
```
- Use Setup Store syntax (not Options Store).
- Prefer actions for business-level mutations and `$patch()` for grouped updates.
- Every async action: handle loading + success + error.
---
## 5. Vue Router
### Route Definitions
```ts
const routes = [
{
path: "/users/:id",
name: "user-detail",
component: () => import("@/pages/UserDetail.vue"), // lazy
props: true, // pass params as props
meta: { requiresAuth: true },
},
];
```
### Navigation Guards
```ts
router.beforeEach((to, from) => {
const { isLoggedIn } = useAuthStore();
if (to.meta.requiresAuth && !isLoggedIn) {
return { name: "login", query: { redirect: to.fullPath } };
}
});
```
### Reactive Route Params
When a component stays mounted but route params change:
```ts
const route = useRoute();
const id = computed(() => route.params.id as string);
watch(id, (newId) => fetchItem(newId));
```
---
## 6. Template Patterns
### Template Syntax
```vue
<!-- v-if/v-else-if/v-else -->
<div v-if="isLoading">Loading...</div>
<div v-else-if="error">Error: {{ error }}</div>
<div v-else>{{ content }}</div>
<!-- v-show for frequent toggles -->
<div v-show="isOpen">Toggled content</div>
<!-- v-for with stable keys -->
<div v-for="item in items" :key="item.id">{{ item.name }}</div>
<!-- Computed filtered list (not v-if + v-for on same element) -->
<div v-for="item in activeItems" :key="item.id">{{ item.name }}</div>
<!-- Event handling -->
<form @submit.prevent="handleSubmit">
<button type="submit">Save</button>
</form>
<!-- v-model -->
<input v-model="name" />
<CustomInput v-model="value" v-model:title="title" />
```
---
## 7. Performance
| Technique | When to Use |
|-----------|-------------|
| `v-memo` | List items that rarely change |
| `v-once` | Content rendered once and static forever |
| `shallowRef()` | Large data structures replaced wholesale |
| `shallowReactive()` | Only top-level properties are reactive |
| `v-show` over `v-if` | Frequent visibility toggles |
| `<KeepAlive :max="10">` | Cache toggled views |
| Lazy routes | `() => import(...)` for non-critical routes |
| `Suspense` | Async component loading with fallback |
---
## 8. Testing
### Stack
- **Vitest** for unit and component tests
- **Vue Test Utils** for mounting and interaction
- **@pinia/testing** for store mocking
- **Playwright** for E2E
### Component Test Pattern
```ts
import { mount } from "@vue/test-utils";
import { createPinia, setActivePinia } from "pinia";
import UserCard from "./UserCard.vue";
beforeEach(() => { setActivePinia(createPinia()); });
it("renders and emits", 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-Specific Patterns
### Auto-Imports
Nuxt auto-imports `ref`, `computed`, `watch`, `useFetch`, `useAsyncData`, etc. Use them directly without importing. For non-Nuxt projects, always import explicitly.
### useAsyncData / useFetch
```ts
const { data: user, pending, error, refresh } = await useAsyncData(
"user", // unique key for caching
() => $fetch(`/api/users/${id}`),
);
const { data: posts } = await useFetch("/api/posts", {
query: { page: 1 },
key: "posts-page-1", // dedupes requests
});
```
### Server Routes
```ts
// server/api/users/[id].ts
export default defineEventHandler(async (event) => {
const { id } = await getValidatedRouterParams(event, z.object({
id: z.string().uuid(),
}).parse);
// ... fetch and return
});
```
### Runtime Config
```ts
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
// server-only
apiSecret: "",
// public (exposed to client)
public: {
apiBase: "https://api.example.com",
},
},
});
```
---
## 10. Vue 3.5+ New APIs
### Reactive Props Destructure
Vue 3.5 stabilized reactive props destructure — destructured variables from `defineProps()` are automatically reactive:
```ts
// Vue 3.5+: destructured props are reactive (no need for toRefs)
const { count = 0, msg = "hello" } = defineProps<{
count?: number;
msg?: string;
}>();
// Limitation: cannot watch destructured prop directly
watch(() => count, (newVal) => { ... }); // PASS getter required
```
### `useTemplateRef()`
Replace name-matched plain refs with `useTemplateRef()` for template references:
```ts
import { useTemplateRef } from "vue";
const inputEl = useTemplateRef<HTMLInputElement>("input");
// "input" matches the ref="input" attribute in template, not the variable name
```
Supports dynamic ref IDs: `useTemplateRef(dynamicRefId)`.
### `onWatcherCleanup()`
Globally importable watcher cleanup API (Vue 3.5+). It must be called synchronously inside the watcher callback:
```ts
import { watch, onWatcherCleanup } from "vue";
watch(userId, async (newId) => {
const controller = new AbortController();
onWatcherCleanup(() => controller.abort());
// ... fetch with signal
});
```
### `useId()`
SSR-stable unique ID generation for form elements and accessibility:
```ts
import { useId } from "vue";
const id = useId();
```
### `defer` Teleport
`<Teleport defer>` allows teleporting to targets rendered in the same cycle:
```vue
<Teleport defer to="#container">Content</Teleport>
<div id="container"></div>
```
### Lazy Hydration (SSR)
`defineAsyncComponent()` now supports `hydrate` strategy:
```ts
import { defineAsyncComponent, hydrateOnVisible } from "vue";
const AsyncComp = defineAsyncComponent({
loader: () => import("./Comp.vue"),
hydrate: hydrateOnVisible(),
});
```
---
## Anti-Patterns
| Anti-Pattern | Why It's Wrong | The Fix |
|-------------|---------------|---------|
| Destructuring `defineProps()` (Vue < 3.5) | Captures snapshot, loses reactivity | Access via `props.xxx` or use `toRefs()` |
| `watch()` on destructured prop (Vue 3.5+) | Compile-time error — destructured props can't be watched directly | Use getter wrapper: `watch(() => count, ...)` |
| `v-if` + `v-for` on same element | Ambiguous execution order | Use computed filtered array |
| `v-for` key = index | Broken state on reorder | Use stable database IDs |
| Mutating props | Violates one-way data flow | Emit events or use `v-model` |
| `v-html` with user content | XSS vulnerability | Sanitize with DOMPurify |
| Mixins in Vue 3 | Opaque, collision-prone | Replace with composables |
| Module-scope side effects in composable | Shared across instances | Scope in `onMounted` + `onUnmounted` |
| `reactive()` for replaceable state | Replacement breaks reactivity | Use `ref()` instead |
| Watcher without cleanup | Memory leaks, race conditions | Use `onCleanup` or `onWatcherCleanup()` (Vue 3.5+) |
| Options API in new Vue 3 code | Ecosystem move to Composition API | Use `<script setup>` |
| Plain ref for template references | No dynamic ref support, name-matching fragile | Use `useTemplateRef()` (Vue 3.5+) |
## Related Skills
- `accessibility` — ARIA, semantic HTML, focus management
- `frontend-patterns` — Cross-framework frontend architecture
- `typescript` — TypeScript best practices applied to Vue projects
- `coding-standards` — General code quality standards
Все файлы
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
Копировать





Дом
