Skip to main content

vue-patterns

Vue.js 3 Composition API 模式、组件架构、响应式最佳实践、Pinia 状态管理、Vue Router 导航和 Nuxt SSR 模式。在 Vue、Nuxt、Vite 或 Pinia 项目中激活。

Source facts

Repository
aaione/everything-claude-code-zh
Last source activity
June 21, 2026 at 01:33
Detected SKILL.md language
Chinese
Stars
30
Forks
14

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
vue-patterns
description
Vue.js 3 Composition API 模式、组件架构、响应式最佳实践、Pinia 状态管理、Vue Router 导航和 Nuxt SSR 模式。在 Vue、Nuxt、Vite 或 Pinia 项目中激活。
origin
ECC
# Vue.js 模式与最佳实践 使用 Composition API(`<script setup>`)进行 Vue.js 3 开发的综合指南,覆盖组件设计、响应式、状态管理、路由、测试和 SSR 模式。Nuxt 专属指引在与原生 Vue 不同处给出。 ## 何时激活 在以下情况激活: - 项目使用 Vue.js(任何版本)、Nuxt、Vite + Vue 或 Pinia。 - 用户问 Vue 组件架构、composables、响应式或状态管理。 - 审查 Vue Single-File Components(`.vue` 文件)。 - 搭建 Vue Router、Pinia stores 或 Vite/Vitest 配置。 - 讨论 Vue 专属性能、安全或 SSR 模式。 --- ## 1. 项目结构 ### 推荐布局(Feature-First) ``` 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 文件夹 | --- ## 2. 组件架构 ### Single-File Component 顺序 ```vue <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> ``` ### Presentational vs Container - **Container 组件**:拥有数据获取、状态和副作用。渲染 presentational 组件。 - **Presentational 组件**:接收 props、emit 事件。无 API 调用、无 store 访问。纯渲染。 ### Props 最佳实践 ```ts // 基于类型的 props,带默认值 interface Props { label: string; variant?: "primary" | "secondary"; disabled?: boolean; items: Item[]; } const props = withDefaults(defineProps<Props>(), { variant: "primary", disabled: false, }); ``` - 始终提供 `type`,并在合适时提供 `required`/`default`。 - Boolean props:`isXxx`、`hasXxx`、`canXxx`。 - 绝不修改 props——改为 emit 事件。 - 对 v-model 绑定,使用 `defineModel()`(Vue 3.4+)或 `modelValue` + `update:modelValue`。 ### 事件 ```ts const emit = defineEmits<{ submit: []; "update:modelValue": [value: string]; select: [id: string, index: number]; }>(); ``` - 在 templates 中用 kebab-case(`@update:model-value`)。 - 在 script 中用 camelCase(`emit("update:modelValue", val)`)。 --- ## 3. Composables(可复用逻辑) ### 结构 ```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); } ``` ### 规则 - 必须以 `use` 前缀开头。 - 返回响应式值(`ref`、`computed`、`reactive`),绝不返回普通 primitives。 - 通过 `MaybeRef` / `toRef()` / `toValue()` 接受响应式输入。 - 在 `onUnmounted` 或 watcher `onCleanup` 中清理副作用。 - 无模块作用域副作用。 ### vs Mixins Composables 完全取代 Vue 2 mixins: - **Mixins**:不透明数据流、source-of-truth 冲突、命名冲突。 - **Composables**:显式 imports、清晰返回值、可组合且 tree-shakable。 --- ## 4. 状态管理 ### 何时用什么 | 模式 | 用例 | |---------|----------| | `ref()` / `reactive()` | 本地组件状态 | | Props + Emits | 父子通信 | | Provide / Inject | Theme、config、plugin API | | Pinia store | 全局、共享、复杂状态 | | Server state composable | 带 cache 的 API 数据(包装 `fetch`/TanStack Query) | ### Pinia Setup Store(首选) ```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 }; }); ``` - 使用 Setup Store 语法(不是 Options Store)。 - 业务级 mutation 优先用 actions,成组更新用 `$patch()`。 - 每个异步 action:处理 loading + success + error。 --- ## 5. Vue Router ### Route 定义 ```ts const routes = [ { path: "/users/:id", name: "user-detail", component: () => import("@/pages/UserDetail.vue"), // 懒加载 props: true, // 把 params 作为 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 } }; } }); ``` ### 响应式 Route Params 当组件保持挂载但 route params 变化时: ```ts const route = useRoute(); const id = computed(() => route.params.id as string); watch(id, (newId) => fetchItem(newId)); ``` --- ## 6. 模板模式 ### 模板语法 ```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 用于频繁切换 --> <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" /> ``` --- ## 7. 性能 | 技术 | 何时使用 | |-----------|-------------| | `v-memo` | 很少变化的列表项 | | `v-once` | 只渲染一次且永远静态的内容 | | `shallowRef()` | 整体替换的大型数据结构 | | `shallowReactive()` | 只有顶层属性需要响应式 | | `v-show` over `v-if` | 频繁的可见性切换 | | `<KeepAlive :max="10">` | 缓存切换的视图 | | 懒加载 routes | `() => import(...)` 用于非关键 routes | | `Suspense` | 带 fallback 的异步组件加载 | --- ## 8. 测试 ### 技术栈 - **Vitest** 用于单元和组件测试 - **Vue Test Utils** 用于挂载和交互 - **@pinia/testing** 用于 store mocking - **Playwright** 用于 E2E ### 组件测试模式 ```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 专属模式 ### Auto-Imports Nuxt 自动导入 `ref`、`computed`、`watch`、`useFetch`、`useAsyncData` 等。直接使用,无需 import。非 Nuxt 项目始终显式 import。 ### useAsyncData / useFetch ```ts 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 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(暴露给客户端) public: { apiBase: "https://api.example.com", }, }, }); ``` --- ## 10. Vue 3.5+ 新 API ### Reactive Props Destructure Vue 3.5 稳定了 reactive props destructure——从 `defineProps()` 解构的变量会自动保持响应式: ```ts // 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: ```ts 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 内同步调用: ```ts import { watch, onWatcherCleanup } from "vue"; watch(userId, async (newId) => { const controller = new AbortController();
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub