前言
团队协作中,统一的开发规范能显著降低沟通成本和维护难度。
本文按 代码审查维度 整理 Vue3 开发规范,适用于 Vue3 + Composition API + TypeScript 技术栈,其他变体可按需调整。
审查维度概览
| 维度 |
权重 |
核心要求 |
| TypeScript 规范 |
15 |
类型完整、禁止 any、interface / type 分工明确 |
| Vue3 语法规范 |
15 |
Composition API、组件命名、Props / 事件声明 |
| 命名与项目结构 |
15 |
目录划分、文件命名、路由与导入路径 |
| 组件设计 |
20 |
Props 类型、粒度控制、通信方式 |
| 代码质量 |
15 |
格式统一、注释、错误处理、禁用的写法 |
| 性能优化 |
20 |
懒加载、响应式精准化、列表渲染、computed 规范 |
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 = {}
|
Vue3 语法规范
- 使用
<script setup lang="ts"> 语法糖,不使用 Options API。
- 组件名使用 PascalCase,模板中使用 kebab-case。
- Props 必须声明类型,使用 TypeScript 类型声明而非运行时声明。
- 事件命名使用 camelCase,如
update:modelValue。
- 稍复杂的组件应做到模板、脚本和样式分离。
单文件组件内部按以下顺序组织:<script> → <template> → <style>。
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>
|
命名与项目结构
命名方式速查
| 命名方式 |
规则 |
示例 |
常见用途 |
| PascalCase |
每个单词首字母大写 |
UserProfile、MyComponent |
Vue 组件名、TS 类 / 接口 / 类型 |
| camelCase |
首单词小写,后续单词首字母大写 |
userName、getList |
变量、函数、Props、对象属性 |
| kebab-case |
全小写,单词间用 - 连接 |
user-profile、get-list |
文件名、CSS 类名、URL 路径 |
| UPPER_SNAKE_CASE |
全大写,单词间用 _ 连接 |
MAX_COUNT、API_BASE_URL |
常量、环境变量 |
推荐目录
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 |
user-profile/、composables/ |
| 组件文件夹 |
PascalCase |
UserProfile/、BaseButton/ |
| 页面组件 |
kebab-case,与路由路径一致 |
user-profile/index.vue |
| 通用组件 |
PascalCase |
UserProfile.vue、BaseButton.vue |
| 组合式函数 |
camelCase,use 前缀 |
useAuth.ts |
| 工具函数 |
camelCase |
formatDate.ts |
| 类型定义 |
.d.ts 或 types.ts 结尾 |
user.d.ts、api.types.ts |
| CSS 类名 |
kebab-case |
user-profile-card |
| 常量 |
UPPER_SNAKE_CASE |
MAX_RETRY_COUNT = 3 |
1 2 3 4
| src/pages/user-profile/index.vue src/pages/user-profile/UserManager/UserManager.vue src/composables/useAuth.ts src/api/user.ts
|
路由与导入
- 路由 path 使用小写 kebab-case,如
/user-profile、/order-detail。
- 动态参数使用 camelCase,如
/user/:userId。
- 使用
@/ 别名指向 src/,避免多层相对路径。
- 导入顺序:第三方库 →
@/ 别名 → 相对路径。
1 2 3 4 5 6 7 8 9 10 11
| import path from 'node:path' import { defineConfig } from 'vite'
export default defineConfig({ resolve: { alias: { '@': path.resolve(__dirname, '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'
|
组件设计
Props 定义
优先使用 TypeScript 类型声明,必须声明类型,禁止 any。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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 或状态管理库。
- 兄弟组件优先通过共同父组件中转,避免事件总线。
代码质量
工程工具
- 包管理使用 pnpm。
- 推荐使用
@antfu/eslint-config 或官方 eslint-plugin-vue 的 flat config 模式,配合 Prettier / oxc 统一格式化。
1
| pnpm add -D eslint @antfu/eslint-config
|
1 2 3 4 5 6 7
| import antfu from '@antfu/eslint-config'
export default antfu({ vue: true, typescript: true, })
|
1 2 3 4 5 6 7 8
| [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true
|
代码格式
注释规范
- 复杂逻辑必须添加注释,说明意图而非描述代码。
- 使用 JSDoc 格式为公共函数添加文档注释。
- TODO 注释格式:
// TODO(作者): 说明
1 2 3 4 5 6 7 8 9 10 11
|
function calcDiscount(price: number, discount: number): number { const validDiscount = Math.max(0, Math.min(1, discount)) return price * validDiscount }
|
错误处理
- 异步操作必须使用
try/catch 或 .catch() 处理错误。
- 不要吞掉错误,至少打印日志或上报监控。
- 组件级错误使用
onErrorCaptured 捕获。
禁止与避免的写法
| 禁止 |
替代 / 说明 |
var |
统一使用 const / let |
== |
统一使用 === |
computed 中产生副作用 |
仅用于派生数据,见下方说明 |
| 直接操作 DOM |
优先使用 Vue 指令和 ref |
| 多层嵌套 |
超过 3 层时拆分函数;需要嵌套时用 Promise 封装 |
| 工具类方法名重复 |
不同工具类中方法名保持唯一 |
代码习惯
- 禁止多层代码嵌套,需要嵌套的使用 Promise 进行封装。
- 工具类方法保证不同的工具类中方法名不要重复。
性能优化
组件懒加载
路由级别使用动态导入,减少首屏加载体积。
1 2 3 4 5 6
| const routes = [ { path: '/dashboard', component: () => import('@/pages/dashboard/index.vue'), }, ]
|
响应式精准化
- 小范围响应式数据用
ref,复杂对象用 reactive。
- 从
reactive 对象解构时使用 toRefs,避免丢失响应性。
1 2
| const state = reactive({ name: '', age: 0 }) const { name } = toRefs(state)
|
列表渲染
1 2 3 4 5 6 7
| <template> <ul> <li v-for="item in list" :key="item.id"> {{ item.name }} </li> </ul> </template>
|
计算属性与缓存
派生数据统一使用 computed,不要在模板中写复杂表达式。
1 2 3 4
| const sortedList = computed(() => [...list.value].sort((a, b) => a.name.localeCompare(b.name)), )
|
computed 中禁止产生副作用——除返回值外不得对系统产生其他影响,包括:
- 修改其他响应式变量
- 调用 API / 发送网络请求
- 操作 DOM 或使用定时器
- 调用会改变外部状态的函数
图片与资源
- 静态图片放
src/assets/ 并通过模块导入,构建时自动处理。
- 外部大图使用懒加载(
loading="lazy" 或 Intersection Observer)。
- 图标优先使用 SVG sprite 或图标库;图片尽量使用 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 标记后当日发版并通知测试人员。
对其他端要求
原型 / 效果图
- 原型上面的模块和下面的页面分类名称定义清晰。
- 效果图不要在原图上添加色块,分类要清晰。
- 设计参考组件库,注意组件分组。
- 设计图页面分组和排序保持一致。
接口
- 文档返回值类型和必填要符合实际情况。
- 按应用业务模块归类,不要产生业务不需要的接口。
- 字段不能缺失,也不能冗余。
- 接口变更后文档要同步更新。
测试