Skip to main content

vue-patterns

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

소스 정보

저장소
aaione/everything-claude-code-zh
최근 소스 활동
2026년 6월 21일 01:33
감지된 SKILL.md 언어
중국어
스타
30
포크
14

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
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();
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기