plugins/vue-coder/skills/vue3-best-practices/SKILL.md
Vue 3 开发最佳实践指南 - Composition API、Script Setup、Pinia、TypeScript 集成及性能优化。 当用户说"Vue 3组件"、"Composition API"、"script setup"、"Pinia"、"Vue 3项目"、"ref reactive"、"defineProps defineEmits"、"Composable"、"Vue 3优化"时使用此技能。 涵盖:Script Setup 与 Composition API、响应式数据选择(ref vs reactive)、组件通信(Props/Emits/v-model/Slots)、Composables 设计模式、Pinia Setup Store、性能优化(v-memo、shallowRef、KeepAlive)。 提供 TypeScript 代码示例、反模式对照表、迁移指南和示例文件引用。
npx skillsauth add protagonistss/ithinku-plugins vue3-best-practicesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
本技能指导开发者编写 模块化、类型安全、高性能 的 Vue 3 应用。
核心原则:
src/
├── assets/ # 静态资源
├── components/ # 通用组件
│ ├── ui/ # 基础 UI 组件
│ └── business/ # 业务组件
├── composables/ # 组合式函数 (use*.ts)
├── stores/ # Pinia stores
├── views/ # 页面组件
├── router/ # 路由配置
├── types/ # TypeScript 类型定义
├── utils/ # 工具函数
├── api/ # API 请求封装
└── App.vue
命名规范:
| 类型 | 规范 | 示例 |
|------|------|------|
| 组件 | PascalCase | UserProfile.vue |
| Composables | camelCase + use 前缀 | useAuth.ts |
| Stores | camelCase + Store 后缀 | userStore.ts |
| 工具函数 | camelCase | formatDate.ts |
<script setup lang="ts">
// ✅ 推荐:显式导入,利于代码阅读和依赖追踪
import { ref, computed, watch, onMounted } from 'vue'
import { useUserStore } from '@/stores/userStore'
// 顶层 await 支持
const data = await fetchInitialData()
// 响应式状态
const count = ref(0)
const doubled = computed(() => count.value * 2)
// Store 使用
const userStore = useUserStore()
</script>
要点:
<script setup lang="ts">,更简洁,运行时性能更好awaitref, computed, watch 等(而非依赖自动导入)| 场景 | 推荐 | 原因 |
|------|------|------|
| 基本类型 | ref | 清晰的 .value 访问 |
| 对象/数组(默认) | reactive | 更直观;解构需 toRefs |
| 需要整体替换/可空对象 | ref | 便于赋新对象与类型约束 |
| 深层嵌套大对象 | reactive | 仅当不解构时使用 |
| 大型外部实例 | shallowRef | 避免不必要的深度响应 |
// ✅ 推荐
const user = ref<User | null>(null)
user.value = { name: 'John' }
// ⚠️ 谨慎使用 reactive
const state = reactive({ items: [] })
// 解构会丢失响应性!
const { items } = state // ❌ items 不再是响应式
// ✅ 使用 toRefs 解构
const { items } = toRefs(state)
// Vue 3.5+ 推荐写法
const { title, count = 0 } = defineProps<{
title: string
count?: number
}>()
// Vue 3.4 及以下
const props = withDefaults(defineProps<{
title: string
count?: number
}>(), {
count: 0
})
注意:解构式 props 需要 Vue 3.5+(或编译选项 propsDestructure: true)。否则解构结果非响应式,建议使用 withDefaults 或保留 props.xxx 访问。
const emit = defineEmits<{
change: [id: number]
update: [value: string]
}>()
// 使用
emit('change', 123)
// 简化双向绑定
const modelValue = defineModel<string>()
const count = defineModel<number>('count', { default: 0 })
defineSlots<{
default: (props: { item: Item }) => any
header: () => any
}>()
// 暴露给父组件的方法/属性
defineExpose({
focus: () => inputRef.value?.focus(),
reset
})
递归组件、调试、DevTools 中必须显式命名:
defineOptions({
name: 'TreeNode', // 递归组件必须
inheritAttrs: false // 禁用属性自动透传
})
何时需要命名: | 场景 | 必要性 | |------|--------| | 递归组件 | ⭐ 必须 | | DevTools 调试 | 推荐 | | KeepAlive include/exclude | 必须 | | Transition 组件 | 推荐 |
<script setup lang="ts">
defineOptions({ inheritAttrs: false })
// 获取透传的属性
const attrs = useAttrs()
</script>
<template>
<!-- 手动绑定到内部元素 -->
<div class="wrapper">
<input v-bind="attrs" />
</div>
</template>
<script setup lang="ts" generic="T extends { id: number }">
defineProps<{
items: T[]
selected?: T
}>()
const emit = defineEmits<{
select: [item: T]
}>()
</script>
// composables/useCounter.ts
import { ref, computed } from 'vue'
export function useCounter(initial = 0) {
const count = ref(initial)
const doubled = computed(() => count.value * 2)
function increment() {
count.value++
}
function reset() {
count.value = initial
}
return {
count,
doubled,
increment,
reset
}
}
// composables/useFetch.ts
import { ref, shallowRef, watchEffect, toValue, type MaybeRefOrGetter } from 'vue'
export function useFetch<T>(url: MaybeRefOrGetter<string>) {
const data = shallowRef<T | null>(null)
const error = shallowRef<Error | null>(null)
const loading = ref(false)
async function execute() {
loading.value = true
error.value = null
try {
const res = await fetch(toValue(url))
data.value = await res.json()
} catch (e) {
error.value = e as Error
} finally {
loading.value = false
}
}
watchEffect(() => {
execute()
})
return { data, error, loading, refresh: execute }
}
注意:MaybeRefOrGetter/toValue 需要 Vue 3.3+。低版本可用 unref 或改为仅接收 Ref。
最佳实践:
use 开头命名this// stores/userStore.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useUserStore = defineStore('user', () => {
// State
const user = ref<User | null>(null)
const token = ref('')
// Getters
const isLoggedIn = computed(() => !!token.value)
const displayName = computed(() => user.value?.name ?? 'Guest')
// Actions
async function login(credentials: LoginDTO) {
const res = await api.login(credentials)
user.value = res.user
token.value = res.token
}
function logout() {
user.value = null
token.value = ''
}
return {
user,
token,
isLoggedIn,
displayName,
login,
logout
}
})
要点:
| ❌ 错误做法 | ✅ 正确做法 |
|-------------|-------------|
| 使用 Mixins | 使用 Composables |
| const { prop } = props 解构 | props.prop 或 toRefs(props) |
| 在 setup 中写 created 逻辑 | 直接写在 setup 顶层 |
| 忘记 .value | 始终在 script 中使用 .value |
| reactive 后解构 | 使用 ref 或 toRefs |
| Options API 混用 | 统一使用 Composition API |
| 技术 | 场景 | 示例 |
|------|------|------|
| v-memo | 大型列表/表格 | v-memo="[item.id, item.selected]" |
| shallowRef | 大型外部实例 | 地图、图表实例 |
| KeepAlive | 缓存组件 | 标签页切换 |
| 路由懒加载 | 所有路由 | () => import('./Page.vue') |
| defineAsyncComponent | 条件渲染组件 | 模态框、抽屉 |
// 路由懒加载
const routes = [
{
path: '/dashboard',
component: () => import('@/views/Dashboard.vue')
}
]
// 异步组件
const HeavyModal = defineAsyncComponent(() =>
import('./HeavyModal.vue')
)
| 分类 | 推荐 | |------|------| | 构建工具 | Vite | | 路由 | Vue Router 4 | | 状态管理 | Pinia | | UI 组件库 | Element Plus / Naive UI / Ant Design Vue | | 样式方案 | UnoCSS / Tailwind CSS | | 测试 | Vitest + Vue Test Utils | | 工具库 | VueUse |
| Options API | Composition API |
|-------------|-----------------|
| data() | ref() / reactive() |
| computed: {} | computed() |
| methods: {} | 普通函数 |
| watch: {} | watch() / watchEffect() |
| created | <script setup> 顶层代码 |
| mounted | onMounted() |
| this.xxx | 直接访问变量 |
| 问题 | 原因 | 解决 |
|------|------|------|
| 数据不更新 | 忘记 .value | 检查 ref 访问 |
| 解构后不响应 | reactive 解构 | 使用 toRefs() |
| computed 不执行 | 未访问 .value | 确保访问响应式依赖 |
| watch 不触发 | 监听了原始值 | 使用 getter 函数 |
| Props 类型错误 | 缺少类型定义 | 添加泛型类型 |
本技能包含以下完整示例,位于 examples/ 目录:
| 文件 | 说明 | |------|------| | component-example.vue | 递归树形组件,展示 defineOptions 命名、插槽透传 | | composable-example.ts | usePagination 分页逻辑封装 | | store-example.ts | Pinia Setup Store 完整示例 |
# 生成 Composable
/vue-coder 提取这段逻辑为一个名为 usePagination 的 Composable 函数。
# 转换 Options API
/vue-coder 将这个 Options API 组件重构为 <script setup lang="ts"> 写法。
# 优化响应式
/vue-coder 检查这段代码中 reactive 的使用是否合理,建议改为 ref。
# 添加类型
/vue-coder 为这个组件的 props 和 emits 添加完整的 TypeScript 类型。
# 性能优化
/vue-coder 分析这个列表组件的性能问题,建议优化方案。
development
Vue 2 维护与开发最佳实践指南 - Options API、Vuex 及向 Vue 3 迁移准备。 当用户说"Vue 2组件"、"Options API"、"Vuex"、"Vue 2项目"、"Vue 2迁移"、"Vue mixin"、"Vue 2最佳实践"时使用此技能。 涵盖:Options API 规范(选项顺序、props 验证)、Vuex 模块化(namespaced modules)、逻辑复用(避免 mixin,使用工具函数)、迁移准备(停止使用 Filters、引入 Composition API 插件)。 提供 Vue 2 代码示例、反模式警告和迁移建议。
development
核心设计能力 - 提供配色、布局、组件样式生成及反模式检查。 当用户说"设计UI"、"生成样式"、"页面布局"、"CSS样式"、"组件设计"、"配色方案"、"设计系统"、"前端样式"、"响应式设计"、"动画效果"时使用此技能。 支持多种设计风格:Neo-Brutalism、Glassmorphism、Editorial、Cyberpunk。 提供配色方案、布局生成、组件样式、微交互动效、响应式网格。拒绝"AI廉价感",追求大胆、独特、细节丰富的设计。 重要特性:提供反模式检查,避免泛滥的渐变、无聊的阴影、默认圆角等平庸设计。
content-media
无障碍设计审查与修复能力。 当用户说"无障碍"、"a11y"、"WCAG"、"键盘导航"、"屏幕阅读器"、"颜色对比度"、"ARIA"、"可访问性"、"辅助功能"、"盲人友好"时使用此技能。 基于 WCAG 2.1 标准,检测图片 Alt 文本缺失、表单 Label 关联、键盘可访问性、颜色对比度不足、ARIA 属性误用等问题。 提供修复代码示例:语义化标签、焦点管理、焦点陷阱、屏幕阅读器支持。输出合规性检查报告和修复建议。
development
分析源代码并生成对应的单元测试用例。 当用户说"生成测试"、"写单元测试"、"添加测试用例"、"测试覆盖"、"单元测试"、"vitest测试"、"jest测试"、"pytest测试"、"测试代码"时使用此技能。 支持多种语言和框架:JavaScript/TypeScript (Jest, Vitest, Mocha)、Python (pytest, unittest)、Java (JUnit 5, TestNG)。 自动生成:正常流程测试、边界条件测试、异常情况测试、Mock 配置。输出遵循 AAA 模式的高质量测试代码。