Guides Vue.js 3 development with Composition API, component architecture, Pinia state management, Vue Router, and Nuxt SSR patterns. Activates in Vue, Nuxt, Vite, or Pinia projects.
How this skill is triggered — by the user, by Claude, or both
Slash command
/everything-claude-code:vue-patternsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
使用 Composition API(`<script setup>`)进行 Vue.js 3 开发的综合指南,覆盖组件设计、响应式、状态管理、路由、测试和 SSR 模式。Nuxt 专属指引在与原生 Vue 不同处给出。
使用 Composition API(<script setup>)进行 Vue.js 3 开发的综合指南,覆盖组件设计、响应式、状态管理、路由、测试和 SSR 模式。Nuxt 专属指引在与原生 Vue 不同处给出。
在以下情况激活:
.vue 文件)。src/
├── api/ # API client 和 endpoint 定义
├── assets/ # 静态资源(图片、字体、图标)
├── components/ # 共享/复用组件
│ ├── base/ # 基础 UI primitives(Button、Input、Modal)
│ └── features/ # Feature 专属共享组件
├── composables/ # 可复用 Composition API 逻辑
├── layouts/ # 页面布局(可选)
├── pages/ # Route 级页面组件
├── router/ # Vue Router 配置
├── stores/ # Pinia stores
├── types/ # TypeScript 类型定义
├── utils/ # 纯工具函数
└── App.vue # 根组件
| 约定 | 何时使用 |
|---|---|
PascalCase.vue | 所有组件(由 vue/multi-word-component-names 强制) |
useCamelCase.ts | Composables |
camelCase.ts | 工具、API clients、types |
kebab-case 目录 | Route segments、feature 文件夹 |
<script setup lang="ts">
// 1. Imports(vue → ecosystem → 绝对 → 相对)
// 2. Props & Emits & Slots
// 3. Composables
// 4. 本地状态(ref/reactive)
// 5. Computed 属性
// 6. Methods
// 7. Watchers
// 8. 生命周期 hooks
</script>
<template>
<!-- Template 内容 -->
</template>
<style scoped>
/* Scoped 样式 */
</style>
// 基于类型的 props,带默认值
interface Props {
label: string;
variant?: "primary" | "secondary";
disabled?: boolean;
items: Item[];
}
const props = withDefaults(defineProps<Props>(), {
variant: "primary",
disabled: false,
});
type,并在合适时提供 required/default。isXxx、hasXxx、canXxx。defineModel()(Vue 3.4+)或 modelValue + update:modelValue。const emit = defineEmits<{
submit: [];
"update:modelValue": [value: string];
select: [id: string, index: number];
}>();
@update:model-value)。emit("update:modelValue", val))。// 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);
}
use 前缀开头。ref、computed、reactive),绝不返回普通 primitives。MaybeRef / toRef() / toValue() 接受响应式输入。onUnmounted 或 watcher onCleanup 中清理副作用。Composables 完全取代 Vue 2 mixins:
| 模式 | 用例 |
|---|---|
ref() / reactive() | 本地组件状态 |
| Props + Emits | 父子通信 |
| Provide / Inject | Theme、config、plugin API |
| Pinia store | 全局、共享、复杂状态 |
| Server state composable | 带 cache 的 API 数据(包装 fetch/TanStack Query) |
// 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 };
});
$patch()。const routes = [
{
path: "/users/:id",
name: "user-detail",
component: () => import("@/pages/UserDetail.vue"), // 懒加载
props: true, // 把 params 作为 props 传入
meta: { requiresAuth: true },
},
];
router.beforeEach((to, from) => {
const { isLoggedIn } = useAuthStore();
if (to.meta.requiresAuth && !isLoggedIn) {
return { name: "login", query: { redirect: to.fullPath } };
}
});
当组件保持挂载但 route params 变化时:
const route = useRoute();
const id = computed(() => route.params.id as string);
watch(id, (newId) => fetchItem(newId));
<!-- 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 用于频繁切换 -->
<div v-show="isOpen">Toggled content</div>
<!-- v-for 带稳定 key -->
<div v-for="item in items" :key="item.id">{{ item.name }}</div>
<!-- Computed 过滤列表(不是同一元素上的 v-if + v-for) -->
<div v-for="item in activeItems" :key="item.id">{{ item.name }}</div>
<!-- 事件处理 -->
<form @submit.prevent="handleSubmit">
<button type="submit">Save</button>
</form>
<!-- v-model -->
<input v-model="name" />
<CustomInput v-model="value" v-model:title="title" />
| 技术 | 何时使用 |
|---|---|
v-memo | 很少变化的列表项 |
v-once | 只渲染一次且永远静态的内容 |
shallowRef() | 整体替换的大型数据结构 |
shallowReactive() | 只有顶层属性需要响应式 |
v-show over v-if | 频繁的可见性切换 |
<KeepAlive :max="10"> | 缓存切换的视图 |
| 懒加载 routes | () => import(...) 用于非关键 routes |
Suspense | 带 fallback 的异步组件加载 |
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"]);
});
Nuxt 自动导入 ref、computed、watch、useFetch、useAsyncData 等。直接使用,无需 import。非 Nuxt 项目始终显式 import。
const { data: user, pending, error, refresh } = await useAsyncData(
"user", // 用于 cache 的唯一 key
() => $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);
// ... fetch and return
});
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
// server-only
apiSecret: "",
// public(暴露给客户端)
public: {
apiBase: "https://api.example.com",
},
},
});
Vue 3.5 稳定了 reactive props destructure——从 defineProps() 解构的变量会自动保持响应式:
// Vue 3.5+:解构 props 是响应式(不需要 toRefs)
const { count = 0, msg = "hello" } = defineProps<{
count?: number;
msg?: string;
}>();
// 限制:不能直接 watch 解构 prop
watch(() => count, (newVal) => { ... }); // 需要 getter
useTemplateRef()用 useTemplateRef() 替代名称匹配的普通 ref 来做 template references:
import { useTemplateRef } from "vue";
const inputEl = useTemplateRef<HTMLInputElement>("input");
// "input" 匹配 template 中的 ref="input" 属性,不是变量名
支持动态 ref IDs:useTemplateRef(dynamicRefId)。
onWatcherCleanup()全局可 import 的 watcher cleanup API(Vue 3.5+)。必须在 watcher callback 内同步调用:
import { watch, onWatcherCleanup } from "vue";
watch(userId, async (newId) => {
const controller = new AbortController();
onWatcherCleanup(() => controller.abort());
// ... 用 signal fetch
});
useId()SSR 稳定的唯一 ID 生成,用于表单元素和可访问性:
import { useId } from "vue";
const id = useId();
defer Teleport<Teleport defer> 允许 teleport 到同一周期内渲染的目标:
<Teleport defer to="#container">Content</Teleport>
<div id="container"></div>
defineAsyncComponent() 现支持 hydrate 策略:
import { defineAsyncComponent, hydrateOnVisible } from "vue";
const AsyncComp = defineAsyncComponent({
loader: () => import("./Comp.vue"),
hydrate: hydrateOnVisible(),
});
| 反模式 | 为什么错 | 修复 |
|---|---|---|
解构 defineProps()(Vue < 3.5) | 捕获快照,丢失响应式 | 通过 props.xxx 访问或用 toRefs() |
对解构 prop watch()(Vue 3.5+) | 编译期错误——解构 props 不能直接 watch | 用 getter 包装:watch(() => count, ...) |
同一元素上 v-if + v-for | 执行顺序歧义 | 用 computed 过滤数组 |
v-for key = index | 重排时状态错乱 | 用稳定数据库 IDs |
| 修改 props | 违反单向数据流 | emit 事件或用 v-model |
用用户内容 v-html | XSS 漏洞 | 用 DOMPurify 净化 |
| Vue 3 中用 Mixins | 不透明、易冲突 | 替换为 composables |
| Composable 中模块作用域副作用 | 跨实例共享 | 在 onMounted + onUnmounted 中限定 |
reactive() 用于可替换状态 | 替换会破坏响应式 | 改用 ref() |
| 无清理的 watcher | 内存泄漏、竞态 | 用 onCleanup 或 onWatcherCleanup()(Vue 3.5+) |
| 新 Vue 3 代码用 Options API | 生态转向 Composition API | 用 <script setup> |
| 用普通 ref 做 template references | 不支持动态 ref、名称匹配脆弱 | 用 useTemplateRef()(Vue 3.5+) |
accessibility — ARIA、语义 HTML、focus 管理frontend-patterns — 跨框架前端架构typescript — 应用于 Vue 项目的 TypeScript 最佳实践coding-standards — 通用代码质量标准npx claudepluginhub aaione/everything-claude-code-zhGuides Vue.js 3 Composition API patterns, component architecture, reactivity, Pinia state management, Vue Router, and Nuxt SSR. Activates for Vue, Nuxt, Vite, or Pinia projects.
Delivers Vue 3 best practices for Composition API script setup, composables, ref/reactive patterns, Pinia state management, and Nuxt apps. Activates on .vue files and Vue triggers.
Provides Vue 3 expertise on Composition API, Pinia stores, Nuxt 3, Vue Router, custom directives, provide/inject, transitions, and Volar config. Use for building components, state design, routing, and reactivity debugging.