vue3-best-practices

本技能指导开发者编写 模块化、类型安全、高性能 的 Vue 3 应用。

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "vue3-best-practices" with this command: npx skills add protagonistss/ithinku-plugins/protagonistss-ithinku-plugins-vue3-best-practices

Vue 3 Best Practices

🌟 技能核心

本技能指导开发者编写 模块化、类型安全、高性能 的 Vue 3 应用。

核心原则:

  • Composition API First

  • 逻辑复用 (Composables)

  • 类型推导优先

  • 单一数据流

📁 推荐项目结构

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

🧠 核心原则

  1. Script Setup 与 Composition API

<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"> ,更简洁,运行时性能更好

  • 支持顶层 await

  • 显式导入 ref , computed , watch 等(而非依赖自动导入)

  1. 响应式数据 (Reactivity)

场景 推荐 原因

基本类型 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)

  1. 组件通信

Props 定义(带默认值)

// 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 访问。

Emits 定义

const emit = defineEmits<{ change: [id: number] update: [value: string] }>()

// 使用 emit('change', 123)

v-model(Vue 3.4+)

// 简化双向绑定 const modelValue = defineModel<string>() const count = defineModel<number>('count', { default: 0 })

Slots 类型化

defineSlots<{ default: (props: { item: Item }) => any header: () => any }>()

Expose

// 暴露给父组件的方法/属性 defineExpose({ focus: () => inputRef.value?.focus(), reset })

  1. 组件命名 (defineOptions)

递归组件、调试、DevTools 中必须显式命名:

defineOptions({ name: 'TreeNode', // 递归组件必须 inheritAttrs: false // 禁用属性自动透传 })

何时需要命名:

场景 必要性

递归组件 ⭐ 必须

DevTools 调试 推荐

KeepAlive include/exclude 必须

Transition 组件 推荐

  1. 属性透传 (inheritAttrs)

<script setup lang="ts"> defineOptions({ inheritAttrs: false })

// 获取透传的属性 const attrs = useAttrs() </script>

<template> <!-- 手动绑定到内部元素 --> <div class="wrapper"> <input v-bind="attrs" /> </div> </template>

  1. 泛型组件(Vue 3.3+)

<script setup lang="ts" generic="T extends { id: number }"> defineProps<{ items: T[] selected?: T }>()

const emit = defineEmits<{ select: [item: T] }>() </script>

🧩 逻辑复用 (Composables)

基本模式

// 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 } }

带异步请求的 Composable

// 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 开头命名

  • ✅ 返回对象包含响应式状态和方法

  • ✅ 优先使用 VueUse 已有工具

  • ❌ 不要在 Composable 中使用 this

📦 状态管理 (Pinia)

Setup Store(推荐)

// 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 } })

要点:

  • 优先使用 Setup Store,与组件写法一致

  • State 保持扁平化

  • Getters = computed

  • Actions 处理同步/异步逻辑

🚫 反模式对照表

❌ 错误做法 ✅ 正确做法

使用 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 → Composition

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 分析这个列表组件的性能问题,建议优化方案。

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

performance-review

No summary provided by upstream source.

Repository SourceNeeds Review
General

vue2-best-practices

No summary provided by upstream source.

Repository SourceNeeds Review
General

commit

No summary provided by upstream source.

Repository SourceNeeds Review
General

ui-design-core

No summary provided by upstream source.

Repository SourceNeeds Review