Vue3 开发规范

前言

团队协作中,统一的开发规范能显著降低沟通成本和维护难度。
本文整理一套简洁通用的 Vue3 开发规范,涵盖代码风格、组件设计、项目结构与性能优化。
适用于 Vue3 + Composition API + TypeScript 技术栈,其他变体可按需调整。

常见命名方式

开发中常见的命名风格主要有以下几种,了解它们有助于在不同场景下选择合适的写法。

命名方式 规则 示例 常见用途
PascalCase(大驼峰) 每个单词首字母大写 UserProfileMyComponent Vue 组件名、TypeScript 类/接口/类型
camelCase(小驼峰) 首单词小写,后续单词首字母大写 userNamegetList 变量、函数、Props、对象属性
kebab-case(短横线) 全小写,单词间用 - 连接 user-profileget-list 文件名、CSS 类名、URL 路径、HTML 属性
snake_case(下划线) 全小写,单词间用 _ 连接 user_name 数据库字段、后端接口参数
UPPER_SNAKE_CASE(大写下划线) 全大写,单词间用 _ 连接 MAX_COUNTAPI_BASE_URL 常量、环境变量

前端 Vue3 项目中,最常用的是 PascalCasecamelCasekebab-case 三种。

基本要求

TypeScript 规范

  • 所有变量、函数参数、返回值必须声明类型,禁止使用 any
  • 优先使用 interface 定义对象结构,type 用于联合类型或工具类型。
  • 枚举使用 const enum 或字符串字面量联合类型。
1
2
3
4
5
6
7
8
9
10
11
// 推荐
interface UserInfo {
id: string
name: string
age: number
}

type Status = 'pending' | 'success' | 'error'

// 不推荐
const data: any = {}

Vue 规范

  • 使用 <script setup lang="ts"> 语法糖,不使用 Options API。
  • 组件名使用 PascalCase,模板中使用 kebab-case。
  • Props 必须声明类型,使用 TypeScript 类型声明而非运行时声明。
  • 事件命名使用 camelCase,如 update:modelValue
  • 组件稍微复杂的要做到模板、脚本和样式分离。

包管理

使用PNPM进行包管理

代码格式

  • 使用 2 空格缩进,不使用 Tab。
  • 字符串统一使用单引号 ',模板字符串使用反引号 `
  • 语句末尾不加分号(遵循项目 ESLint 配置)。
  • 每行不超过 100 个字符,超长时换行对齐。

注释规范

  • 复杂逻辑必须添加注释,说明意图而非描述代码。
  • 使用 JSDoc 格式为公共函数添加文档注释。
  • TODO 注释格式:// TODO(作者): 说明
1
2
3
4
5
6
7
8
9
10
11
12
/**
* 计算用户折扣价格
* @param price - 原价
* @param discount - 折扣比例 (0-1)
*/
function calcDiscount(price: number, discount: number): number {
// 折扣范围限制在 0-1 之间
const validDiscount = Math.max(0, Math.min(1, discount))
return price * validDiscount
}

// TODO(zhangsan): 待对接支付接口

错误处理

  • 异步操作必须使用 try/catch.catch() 处理错误。
  • 不要吞掉错误,至少打印日志或上报监控。
  • 组件级错误使用 onErrorCaptured 捕获。

避免的写法

  • 禁止使用 var,统一使用 const / let
  • 禁止使用 ==,统一使用 ===
  • 禁止在 computed 中产生副作用。
  • 禁止直接操作 DOM,优先使用 Vue 指令和 ref。
  • 避免过深的嵌套,超过 3 层时考虑拆分函数。

代码习惯

  • 禁止多层代码嵌套,需要嵌套的使用Promise进行封装。
  • 工具类方法保证不同的工具类中方法名不要重复。

项目结构

推荐目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
src/
├── api/ # 接口请求
├── assets/ # 静态资源
├── components/ # 公共组件
│ └── ui/ # 通用 UI 组件
├── composables/ # 组合式函数
├── constants/ # 常量
├── layouts/ # 布局组件
├── pages/ # 页面组件
├── router/ # 路由配置
├── stores/ # 状态管理
├── styles/ # 全局样式
├── types/ # 类型定义
└── utils/ # 工具函数

文件命名

页面文件与路由路径保持一致,使用 kebab-case。
类型定义文件以 .d.tstypes.ts 结尾。
组合式函数以 use 开头,如 useAuth.ts

1
2
3
4
5
6
src/pages/user-profile/index.vue
src/pages/user-profile/UserManager/UserManager.vue
src/pages/user-profile/UserManager/UserManager.ts
src/pages/user-profile/UserManager/UserManager.less
src/composables/useAuth.ts
src/api/user.ts

路径别名

vite.config.ts 中配置路径别名,避免多层相对路径。

1
2
3
4
5
6
7
8
9
10
import path from 'node:path'
import { defineConfig } from 'vite'

export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
},
},
})

路径规范

文件夹命名

  • 普通文件夹使用 kebab-case,如 user-profile/composables/
  • 组件文件夹使用 PascalCase,如 UserProfile/BaseButton/

文件命名

  • 页面组件与路由路径保持一致,使用 kebab-case,如 user-profile/index.vue
  • 通用组件使用 PascalCase,如 UserProfile.vueBaseButton.vue
  • 工具函数、组合式函数使用 camelCase,如 useAuth.tsformatDate.ts
  • 类型定义文件以 .d.tstypes.ts 结尾,如 user.d.tsapi.types.ts

路由路径

  • 路由 path 使用小写,多个单词用 - 连接,如 /user-profile/order-detail
  • 动态参数使用 camelCase,如 /user/:userId

导入路径

  • 使用 @/ 别名指向 src/ 目录,避免多层相对路径。
  • 第三方库放最前,@/ 别名次之,相对路径放最后。
1
2
3
4
5
6
7
8
// 推荐
import { ref } from 'vue'
import { useRoute } from 'vue-router'
import UserProfile from '@/components/UserProfile.vue'
import { formatDate } from '@/utils/formatDate'

// 不推荐
import UserProfile from '../../../components/UserProfile.vue'

代码风格

命名规范

组件文件名使用 PascalCase,如 UserProfile.vue
工具函数、变量使用 camelCase,常量使用 UPPER_SNAKE_CASE
CSS 类名使用 kebab-case,如 user-profile-card

1
2
3
4
5
6
7
8
9
10
// 组件引用
import UserProfile from '@/components/UserProfile.vue'

// 常量
const MAX_RETRY_COUNT = 3

// 工具函数
function getUserInfo(userId: string) {
// ...
}

ESLint 配置

推荐使用 @antfu/eslint-config 或官方 eslint-plugin-vue 的 flat config 模式。

1
pnpm add -D eslint @antfu/eslint-config

在项目根目录创建 eslint.config.js

1
2
3
4
5
6
import antfu from '@antfu/eslint-config'

export default antfu({
vue: true,
typescript: true,
})

oxc/pretter

代码格式

使用 Prettier 或 ESLint 统一格式化,配置 .editorconfig 保持团队一致。

1
2
3
4
5
6
7
8
# .editorconfig
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

组件设计

组件结构

单文件组件内部按以下顺序组织代码块:<script><template><style>
<script> 中使用 <script setup lang="ts"> 语法糖,保持代码简洁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<script setup lang="ts">
// 1. props / emits
const props = defineProps<{ title: string }>()
const emit = defineEmits<{ close: [] }>()

// 2. 响应式状态
const visible = ref(false)

// 3. 计算属性
const displayTitle = computed(() => props.title || '默认标题')

// 4. 方法
function handleClose() {
visible.value = false
emit('close')
}
</script>

<template>
<div v-if="visible" class="modal">
<h2>{{ displayTitle }}</h2>
<button @click="handleClose">关闭</button>
</div>
</template>

Props 定义

优先使用 TypeScript 类型声明的方式定义 Props,而非运行时声明。
必须声明类型,避免使用 any

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 推荐
const props = defineProps<{
id: string
title: string
count?: number
}>()

// 带默认值
const props = withDefaults(
defineProps<{
title: string
count?: number
}>(),
{
count: 0,
},
)

组件粒度

单一组件代码不超过 300 行,超过时应拆分子组件。
通用 UI 元素(按钮、弹窗、表格)抽到 components/ui/ 目录。
业务组件按功能模块分目录存放。

组件通信

父子通信用 props + emit,不要直接修改 props。
跨层级通信用 provide / inject 或状态管理库。
兄弟组件优先通过共同父组件中转,避免事件总线。

性能优化

组件懒加载

路由级别使用动态导入实现懒加载,减少首屏加载体积。

1
2
3
4
5
6
const routes = [
{
path: '/dashboard',
component: () => import('@/pages/dashboard/index.vue'),
},
]

响应式精准化

小范围响应式数据用 ref,复杂对象用 reactive
reactive 对象解构时使用 toRefs,避免丢失响应性。

1
2
3
4
5
6
7
const state = reactive({ name: '', age: 0 })

// 丢失响应性
// const { name } = state

// 保持响应性
const { name } = toRefs(state)

列表渲染

v-for 必须提供稳定且唯一的 key,不要使用 index 作为 key
大数据列表考虑使用虚拟滚动组件(如 vue-virtual-scroller)。

Getting Started | Vue Virtual Scroller

1
2
3
4
5
6
7
<template>
<ul>
<li v-for="item in list" :key="item.id">
{{ item.name }}
</li>
</ul>
</template>

计算属性与缓存

派生数据统一使用 computed,不要在模板中写复杂表达式。
避免在 computed 中产生副作用。

1
2
3
4
5
6
// 推荐
const sortedList = computed(() =>
[...list.value].sort((a, b) => a.name.localeCompare(b.name)),
)

// 不推荐:模板中直接 list.toSorted(...)

computed 的上下文中,副作用指的是除了返回值以外,对系统产生的任何其他影响

常见的违规操作包括:

  • ❌ 修改其他响应式变量(如 ref, reactive, data
  • ❌ 调用 API / 发送网络请求 (fetch, axios)
  • ❌ 操作 DOM (document.getElementById, window.scrollTo)
  • ❌ 使用定时器 (setTimeout, setInterval)
  • ❌ 打印日志 (console.log —— 虽然无害但属于副作用)
  • ❌ 调用会改变外部状态的函数

图片与资源

静态图片放 src/assets/ 并通过模块导入,构建时自动处理。

外部大图使用懒加载指令或 Intersection Observer。

图标优先使用 SVG sprite 或图标库,减少 HTTP 请求。

图片尽量使用WebP。

1
2
3
4
5
6
7
<script setup lang="ts">
import logo from '@/assets/logo.png'
</script>

<template>
<img :src="logo" alt="logo" loading="lazy" />
</template>

测试相关

已改BUG标记后当日发版并通知测试人员

对其他端要求

原型/效果图

  • 原型上面的模块和下面的页面分类名称定义清晰
  • 效果图不要在原图上添加色块、分类要清晰
  • 设计参考组件库
  • 设计效果注意组件分组
  • 设计图页面分组和排序

接口

  • 文档返回值类型和必填要符合实际情况

  • 要按应用业务模块归类

  • 不要产生业务不需要的接口

  • 字段不能缺失,也不能冗余

  • 接口变更后文档要同步更新

测试

  • 要有版本机制,杜绝只有主干的模式