Vue 项目规范
本规范涵盖 Vue2 和 Vue3 的编码规范,以 Vue 官方风格指南 和 Vue3 官方文档 为基础制定。
版本支持说明
- Vue2:选项式 API(Options API)
- Vue3:组合式 API(Composition API)和选项式 API
- 推荐:新项目使用 Vue3 + 组合式 API;现有 Vue2 项目可逐步迁移到 Vue3
1. Vue 编码基础
所有 Vue 项目均需遵循以下基础规范。
1. 1 组件规范
- 组件名为多个单词。 组件名应该始终是多个单词组成(大于等于 2),且命名规范为
KebabCase格式。这样做可以避免跟现有的以及未来的 HTML 元素相冲突,因为所有的 HTML 元素名称都是单个单词的。
// 正例
export default {
name: 'TodoItem'
// ...
};
// 反例
export default {
name: 'Todo',
// ...
}
export default {
name: 'todo-item',
// ...
}- 组件文件名为
pascal-case格式
// 正例
components/
|- my-component.vue
// 反例
components/
|- myComponent.vue
|- MyComponent.vue- 基础组件文件名为 base 开头,使用完整单词而不是缩写。
// 正例
components/
|- base-button.vue
|- base-table.vue
|- base-icon.vue
// 反例
components/
|- MyButton.vue
|- VueTable.vue
|- Icon.vue- 和父组件紧密耦合的子组件应该以父组件名作为前缀命名
// 正例
components/
|- todo-list.vue
|- todo-list-item.vue
|- todo-list-item-button.vue
|- user-profile-options.vue (完整单词)
// 反例
components/
|- TodoList.vue
|- TodoItem.vue
|- TodoButton.vue
|- UProfOpts.vue (使用了缩写)- 在 Template 模版中使用组件,应使用
PascalCase模式,并且使用自闭合组件。
// 正例
<!-- 在单文件组件、字符串模板和 JSX 中 -->
<MyComponent />
<Row><Table :column="data"/></Row>
// 反例
<my-component /> <row><table :column="data"/></row>- 组件的 data 必须是一个函数
// 正例
export default {
data () {
return {
name: 'jack'
}
}
}
// 反例
export default {
data: {
name: 'jack'
}
}- Prop 定义应该尽量详细
- 必须使用 camelCase 驼峰命名
- 必须指定类型
- 必须加上注释,表明其含义
- 必须加上 required 或者 default,两者二选其一
- 如果有业务需要,必须加上 validator 验证
export default {
props: {
// 组件状态,用于控制组件的颜色
status: {
type: String,
required: true,
validator: function (value) {
return [
'succ',
'info',
'error'
].indexOf(value) !== -1
}
},
// 用户级别,用于显示皇冠个数
userLevel: {
type: String,
required: true
}
}
}- 为组件样式设置作用域
<template>
<button class="btn btn-close">X</button>
</template>
<!-- 使用 `scoped` 特性 -->
<style scoped>
.btn-close {
background-color: red;
}
</style>- 如果特性元素较多,应该主动换行。
<template>
<button
class="btn btn-close"
id="myButton">
X
</button>
</template>1.2 模板中使用简单的表达式
组件模板应该只包含简单的表达式,复杂的表达式则应该重构为计算属性或方法。复杂表达式会让你的模板变得不那么声明式。我们应该尽量描述应该出现的是什么,而非如何计算那个值。而且计算属性和方法使得代码可以重用。
// 正例
<template>
<p>{{ normalizedFullName }}</p>
</template>
// 复杂表达式已经移入一个计算属性
computed: {
normalizedFullName: function () {
return this.fullName.split(' ').map(function (word) {
return word[0].toUpperCase() + word.slice(1)
}).join(' ')
}
}
// 反例
<template>
<p>
{{
fullName.split(' ').map(function (word) {
return word[0].toUpperCase() + word.slice(1)
}).join(' ')
}}
</p>
</template>1.3 指令都使用缩写形式
指令推荐都使用缩写形式,(用 : 表示 v-bind: 、用 @ 表示 v-on: 和用 # 表示 v-slot:)
// 正例
<input
@input="onInput"
@focus="onFocus"
>
// 反例
<input
v-on:input="onInput"
@focus="onFocus"
>1.4 标签顺序保持一致
单文件组件应该总是让标签顺序保持为
<template>...</template>
<script>...</script>
<style scoped>...</style>1.5 必须为 v-for 设置键值 key
1.6 v-show 与 v-if 选择
如果运行时,需要非常频繁地切换,使用 v-show ;如果在运行时,条件很少改变,使用 v-if。
1.7 script 标签内部结构顺序
Vue2 选项式 API 顺序: components > props > data > computed > watch > filter > 钩子函数(钩子函数按其执行顺序) > methods
export default {
name: 'MyComponent',
components: { ... },
props: { ... },
data() { ... },
computed: { ... },
watch: { ... },
filters: { ... },
beforeCreate() { ... },
created() { ... },
beforeMount() { ... },
mounted() { ... },
beforeUpdate() { ... },
updated() { ... },
beforeDestroy() { ... },
destroyed() { ... },
methods: { ... }
}Vue3 组合式 API 建议顺序:
- 导入声明
- Props 定义
- Emits 定义
- 响应式数据(ref, reactive)
- 计算属性(computed)
- 监听器(watch)
- 生命周期钩子
- 方法定义
- 暴露内容
<script setup>
import { ref, reactive, computed, watch, onMounted } from 'vue'
// Props 定义
const props = defineProps({
// ...
})
// Emits 定义
const emit = defineEmits(['update', 'change'])
// 响应式数据
const count = ref(0)
const state = reactive({ ... })
// 计算属性
const doubleCount = computed(() => count.value * 2)
// 监听器
watch(count, (newVal) => { ... })
// 生命周期
onMounted(() => { ... })
// 方法定义
const handleClick = () => { ... }
// 暴露给父组件的内容
defineExpose({ ... })
</script>1.8 Vue Router 规范
- 页面跳转数据传递使用路由参数 页面跳转,例如 A 页面跳转到 B 页面,需要将 A 页面的数据传递到 B 页面,推荐使用路由参数进行传参,而不是将需要传递的数据保存 vuex,然后在 B 页面取出 vuex 的数据,因为如果在 B 页面刷新会导致 vuex 数据丢失,导致 B 页面无法正常显示数据。
let id = ' 123';
this.$router.push({ name: 'userCenter', query: { id: id } });- 使用路由懒加载(延迟加载)机制
{
path: '/uploadAttachment',
name: 'uploadAttachment',
meta: {
title: '上传附件'
},
component: () => import('@/view/components/uploadAttachment/index.vue')
},- router 中的命名规范
- path、childrenPoints 命名规范采用
kebab-case命名规范(尽量vue文件的目录结构保持一致,因为目录、文件名都是kebab-case,这样很方便找到对应的文件) - name 命名规范采用
KebabCase命名规范且和component组件名保持一致!(因为要保持keep-alive特性,keep-alive按照component的name进行缓存,所以两者必须高度保持一致)
// 动态加载
export const reload = [
{
path: '/reload',
name: 'reload',
component: Main,
meta: {
title: '动态加载',
icon: 'icon iconfont'
},
children: [
{
path: '/reload/smart-reload-list',
name: 'SmartReloadList',
meta: {
title: 'SmartReload',
childrenPoints: [
{
title: '查询',
name: 'smart-reload-search'
},
{
title: '执行reload',
name: 'smart-reload-update'
},
{
title: '查看执行结果',
name: 'smart-reload-result'
}
]
},
component: () =>
import('@/views/reload/smart-reload/smart-reload-list.vue')
}
]
}
];- router 中的 path 命名规范 path除了采用
kebab-case命名规范以外,必须以/开头,即使是children里的path也要以/开头。如下示例 目的: 经常有这样的场景:某个页面有问题,要立刻找到这个vue文件,如果不用以/开头,path为parent和children组成的,可能经常需要在router文件里搜索多次才能找到,而如果以/开头,则能立刻搜索到对应的组件;
{
path: '/file',
name: 'File',
component: Main,
meta: {
title: '文件服务',
icon: 'ios-cloud-upload'
},
children: [
{
path: '/file/file-list',
name: 'FileList',
component: () => import('@/views/file/file-list.vue')
},
{
path: '/file/file-add',
name: 'FileAdd',
component: () => import('@/views/file/file-add.vue')
},
{
path: '/file/file-update',
name: 'FileUpdate',
component: () => import('@/views/file/file-update.vue')
}
]
}2. Vue3 组合式 API 规范
2.1 响应式数据使用规范
使用 ref 和 reactive 的场景:
- 基本类型数据使用
ref - 对象类型数据优先使用
reactive,复杂场景使用ref - 需要整体替换对象时使用
ref
<script setup>
import { ref, reactive } from 'vue'
// ✅ 正例:基本类型使用 ref
const count = ref(0)
const message = ref('hello')
// ✅ 正例:对象类型使用 reactive
const user = reactive({
name: 'John',
age: 25,
address: {
city: 'Beijing'
}
})
// ✅ 正例:需要整体替换时使用 ref
const config = ref({
theme: 'dark',
lang: 'zh-CN'
})
// ❌ 反例:基本类型使用 reactive(会失去响应性)
const age = reactive(25) // 错误
</script>2.2 计算属性规范
计算属性命名:
- 必须以
use开头或具有明确语义 - 避免副作用,只做计算
<script setup>
import { computed, ref } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
// ✅ 正例:语义明确的计算属性
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
// ✅ 正例:复杂的计算逻辑
const useFormattedUserInfo = computed(() => {
return {
fullName: `${firstName.value} ${lastName.value}`,
initials: `${firstName.value[0]}${lastName.value[0]}`,
displayName: `${lastName.value}, ${firstName.value}`
}
})
// ❌ 反例:计算属性中有副作用
const userInfo = computed(() => {
console.log('计算用户信息') // 不应该有副作用
return `${firstName.value} ${lastName.value}`
})
</script>2.3 监听器规范
使用场景:
- 数据变化需要执行异步操作时使用
watch - 简单的数据变化且需要知道新值旧值时使用
watch - 需要监听多个数据源时使用
watchEffect
<script setup>
import { ref, watch, watchEffect } from 'vue'
const count = ref(0)
const name = ref('')
// ✅ 正例:监听单个数据源
watch(count, (newVal, oldVal) => {
console.log(`count changed from ${oldVal} to ${newVal}`)
})
// ✅ 正例:监听多个数据源
watch([count, name], ([newCount, newName], [oldCount, oldName]) => {
console.log('Data changed', { newCount, newName, oldCount, oldName })
})
// ✅ 正例:立即执行监听
watch(
() => props.userId,
(newId) => {
fetchUserData(newId)
},
{ immediate: true }
)
// ✅ 正例:使用 watchEffect 自动追踪依赖
watchEffect(() => {
console.log(`Current state: count=${count.value}, name=${name.value}`)
})
// ❌ 反例:在 watch 中直接修改监听的数据
watch(count, (newVal) => {
count.value = newVal + 1 // 可能造成无限循环
})
</script>2.4 生命周期钩子规范
Vue3 组合式 API 生命周期钩子:
onBeforeMount→ 组件挂载前onMounted→ 组件挂载后onBeforeUpdate→ 组件更新前onUpdated→ 组件更新后onBeforeUnmount→ 组件卸载前onUnmounted→ 组件卸载后
<script setup>
import { onMounted, onBeforeUnmount, ref } from 'vue'
const timer = ref(null)
onMounted(() => {
console.log('组件已挂载')
// 设置定时器
timer.value = setInterval(() => {
console.log('定时执行')
}, 1000)
})
onBeforeUnmount(() => {
// 清理定时器
if (timer.value) {
clearInterval(timer.value)
timer.value = null
}
})
</script>2.5 Props 和 Emits 规范
Props 定义:
- 使用 TypeScript 时必须定义类型
- 提供默认值和验证函数
- 使用
readonly保护 props 数据
<script setup lang="ts">
import { computed } from 'vue'
interface Props {
id: number
title?: string
status: 'active' | 'inactive'
metadata?: Record<string, any>
}
const props = withDefaults(defineProps<Props>(), {
title: 'Default Title',
metadata: () => ({})
})
// ✅ 正例:使用 computed 处理 props
const isActive = computed(() => props.status === 'active')
// ❌ 反例:直接修改 props
// props.status = 'active' // 错误:不能直接修改 props
</script>
<!-- Emits 定义 -->
<script setup>
const emit = defineEmits<{
// 确定的事件类型
(e: 'update', value: string): void
(e: 'submit', data: { id: number; name: string }): void
// 可选的事件类型
(e: 'cancel')?: void
}>()
// ✅ 正例:触发事件
const handleSubmit = () => {
emit('submit', { id: 1, name: 'John' })
}
</script>2.6 自定义 Hook 规范
命名规范:
- 必须以
use开头 - 语义明确,功能单一
- 返回响应式数据或方法
// hooks/useCounter.ts
import { ref, computed } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const doubled = computed(() => count.value * 2)
const increment = () => {
count.value++
}
const decrement = () => {
count.value--
}
const reset = () => {
count.value = initialValue
}
return {
count,
doubled,
increment,
decrement,
reset
}
}
// 使用示例
<script setup>
import { useCounter } from '@/hooks/useCounter'
const { count, doubled, increment, decrement, reset } = useCounter(10)
</script>3. Vue 项目目录规范
3.1 基础规范
vue 项目中的所有命名一定要与后端命名统一。 比如权限:后端 privilege, 前端无论 router , store, api 等都必须使用 privilege 单词!
3.2 Vue3 项目目录建议
推荐目录结构(Vue3 + TypeScript):
src/
|-- api/ # API 接口
| |-- modules/
| | |-- user.ts
| | |-- auth.ts
| |-- index.ts
|-- assets/ # 静态资源
| |-- images/
| |-- icons/
| |-- styles/
|-- components/ # 公共组件
| |-- common/ # 通用组件
| |-- business/ # 业务组件
| |-- base/ # 基础组件
|-- composables/ # 自定义 Hook(Vue3 新增)
| |-- useCounter.ts
| |-- useRequest.ts
| |-- useAuth.ts
|-- constants/ # 常量定义
| |-- index.ts
| |-- enum.ts
|-- directives/ # 自定义指令
|-- hooks/ # 兼容旧版本的 hooks
|-- layouts/ # 布局组件
| |-- DefaultLayout.vue
| |-- AdminLayout.vue
|-- plugins/ # 插件配置
|-- router/ # 路由配置
| |-- modules/
| |-- index.ts
|-- stores/ # 状态管理(Pinia)
| |-- modules/
| | |-- user.ts
| | |-- app.ts
| |-- index.ts
|-- types/ # TypeScript 类型定义
| |-- api.ts
| |-- user.ts
|-- utils/ # 工具函数
| |-- request.ts
| |-- storage.ts
|-- views/ # 页面组件
| |-- user/
| | |-- components/
| | |-- UserList.vue
| | |-- UserDetail.vue2.2 使用 Vue-cli 脚手架
使用 vue-cli3 来初始化项目,项目名按照上面的命名规范。
2.3 目录说明
目录名按照上面的命名规范,其中 components 组件用大写驼峰,其余除 components 组件目录外的所有目录均使用 kebab-case 命名。
src 源码目录
|-- api 所有api接口
|-- assets 静态资源,images, icons, styles等
|-- components 公用组件
|-- config 配置信息
|-- constants 常量信息,项目所有Enum, 全局常量等
|-- directives 自定义指令
|-- filters 过滤器,全局工具
|-- datas 模拟数据,临时存放
|-- lib 外部引用的插件存放及修改文件
|-- mock 模拟接口,临时存放
|-- plugins 插件,全局使用
|-- router 路由,统一管理
|-- store vuex, 统一管理
|-- themes 自定义样式主题
|-- views 视图目录
| |-- role role模块名
| |-- |-- role-list.vue role列表页面
| |-- |-- role-add.vue role新建页面
| |-- |-- role-update.vue role更新页面
| |-- |-- index.less role模块样式
| |-- |-- components role模块通用组件文件夹
| |-- employee employee模块- api 目录 文件、变量命名要与后端保持一致。 此目录对应后端 API 接口,按照后端一个 controller 一个 api js 文件。若项目较大时,可以按照业务划分子目录,并与后端保持一致。 api 中的方法名字要与后端 api url 尽量保持语义高度一致性。 对于 api 中的每个方法要添加注释,注释与后端 swagger 文档保持一致。
后端 url: EmployeeController.java
/employee/add
/employee/delete/{id}
/employee/update
前端: employee.js
// 添加员工
addEmployee: (data) => {
return postAxios('/employee/add', data)
},
// 更新员工信息
updateEmployee: (data) => {
return postAxios('/employee/update', data)
},
// 删除员工
deleteEmployee: (employeeId) => {
return postAxios('/employee/delete/' + employeeId)
},- assets 目录 assets 为静态资源,里面存放 images, styles, icons 等静态资源,静态资源命名格式为
kebab-case
|assets
|-- icons
|-- images
| |-- background-color.png
| |-- upload-header.png
|-- styles- components 目录 此目录应按照组件进行目录划分,目录命名为
kebab-case,组件命名规则也为kebab-case
|components
|-- markdown-editor
| |-- index.vue
| |-- index.js
| |-- index.less- constants 目录 此目录存放项目所有常量,如果常量在 vue 中使用,请使用 vue-enum 插件(https://www.npmjs.com/package/vue-enum) 目录结构:
|constants
|-- index.js
|-- role.js
|-- employee.jsemployee.js
export const EMPLOYEE_STATUS = {
NORMAL: {
value: 1,
desc: '正常'
},
DISABLED: {
value: 1,
desc: '禁用'
},
DELETED: {
value: 2,
desc: '已删除'
}
};
export default {
EMPLOYEE_STATUS
};3.5 router 与 store 目录
Router 目录规范:
- router 目录一定要将业务进行拆分,不能放到一个文件里
- router 尽量按照 views 中的结构保持一致
- 使用 TypeScript 时推荐模块化导入
// router/modules/user.ts
export const userRoutes = [
{
path: '/user',
name: 'User',
component: () => import('@/layouts/UserLayout.vue'),
children: [
{
path: '/user/list',
name: 'UserList',
component: () => import('@/views/user/UserList.vue'),
meta: { title: '用户列表' }
}
]
}
]
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import { userRoutes } from './modules/user'
const routes = [
...userRoutes,
// 其他模块路由
]Store 目录规范(Vue3 推荐 Pinia):
- store 按照业务进行拆分不同的文件
- 使用 Composition API 风格的 store
- 每个模块包含 state、getters、actions
// stores/user.ts
import { defineStore } from 'pinia'
import type { UserState } from '@/types/user'
export const useUserStore = defineStore('user', {
state: (): UserState => ({
userInfo: null,
token: '',
permissions: []
}),
getters: {
isLoggedIn: (state) => !!state.token,
userName: (state) => state.userInfo?.name || 'Guest'
},
actions: {
async login(credentials: LoginCredentials) {
try {
const response = await userApi.login(credentials)
this.setUserInfo(response.data)
return response
} catch (error) {
throw error
}
},
setUserInfo(userInfo: UserInfo) {
this.userInfo = userInfo
this.token = userInfo.token
},
logout() {
this.userInfo = null
this.token = ''
this.permissions = []
}
}
})- views 目录 命名要与后端、router、api 等保持一致 components 中组件要使用
kebab-case规则
|-- views 视图目录
| |-- role role模块名
| | |-- role-list.vue role列表页面
| | |-- role-add.vue role新建页面
| | |-- role-update.vue role更新页面
| | |-- index.less role模块样式
| | |-- components role模块通用组件文件夹
| | | |-- role-header.vue role头部组件
| | | |-- role-modal.vue role弹出框组件
| |-- employee employee模块
| |-- behavior-log 行为日志log模块
| |-- code-generator 代码生成器模块2.4 注释说明
必须加注释的地方
- 公共组件使用说明
- api 目录的接口 js 文件必须加注释
- store 中的 state, mutation, action 等必须加注释
- vue 文件中的 template 必须加注释,若文件较大添加 start end 注释
- vue 文件的 methods,每个 method 必须添加注释
- vue 文件的 data, 非常见单词要加注释
2.5 其他
- 尽量不要手动操作 DOM 因使用 vue 框架,所以在项目开发中尽量使用 vue 的数据驱动更新 DOM,尽量(不到万不得已)不要手动操作 DOM,包括:增删改 dom 元素、以及更改样式、添加事件等。
4. Vue3 特有规范
4.1 Teleport 使用规范
使用场景:
- 模态框、弹窗等需要脱离组件层级结构的场景
- 全局提示消息
- 下拉菜单等需要避免 z-index 问题的组件
<template>
<div class="container">
<button @click="showModal = true">打开模态框</button>
<!-- ✅ 正例:使用 Teleport 渲染到 body -->
<Teleport to="body">
<Modal v-if="showModal" @close="showModal = false">
<p>这是模态框内容</p>
</Modal>
</Teleport>
<!-- ✅ 正例:渲染到指定容器 -->
<Teleport to="#notifications">
<Notification v-if="notification" :message="notification" />
</Teleport>
</div>
</template>4.2 Suspense 使用规范
Suspense 用于异步组件加载:
- 提供加载中状态
- 错误边界处理
- 异步组件的优雅降级
<template>
<!-- ✅ 正例:使用 Suspense 处理异步组件 -->
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div class="loading">加载中...</div>
</template>
</Suspense>
<!-- ✅ 正例:带错误处理的 Suspense -->
<Suspense>
<template #default>
<ErrorBoundary>
<AsyncWithErrorHandling />
</ErrorBoundary>
</template>
<template #fallback>
<LoadingSpinner />
</template>
</Suspense>
</template>4.3 Provide / Inject 规范
使用场景:
- 跨层级组件通信
- 主题配置
- 全局配置传递
<!-- 父组件 -->
<script setup>
import { provide, ref } from 'vue'
const theme = ref('light')
const userConfig = ref({
language: 'zh-CN',
timezone: 'Asia/Shanghai'
})
// ✅ 正例:提供响应式数据
provide('theme', theme)
provide('userConfig', userConfig)
// ✅ 正例:提供方法
provide('changeTheme', (newTheme: string) => {
theme.value = newTheme
})
</script>
<!-- 子组件 -->
<script setup>
import { inject } from 'vue'
// ✅ 正例:注入数据
const theme = inject('theme')
const userConfig = inject('userConfig')
// ✅ 正例:带默认值的注入
const changeTheme = inject('changeTheme', () => {
console.warn('changeTheme method not provided')
})
// ✅ 正例:使用 TypeScript 类型保护
const theme = inject<Ref<string>>('theme')
</script>5. 性能优化规范
5.1 组件懒加载
路由懒加载:
// ✅ 正例:路由懒加载
const routes = [
{
path: '/user',
component: () => import('@/views/user/index.vue')
}
]组件懒加载:
<script setup>
import { defineAsyncComponent } from 'vue'
// ✅ 正例:异步组件加载
const AsyncComponent = defineAsyncComponent({
loader: () => import('@/components/HeavyComponent.vue'),
loadingComponent: LoadingSpinner,
errorComponent: ErrorMessage,
delay: 200,
timeout: 3000
})
// ✅ 正例:带 Suspense 的异步组件
const AsyncModal = defineAsyncComponent(() => import('@/components/Modal.vue'))
</script>5.2 v-memo 优化
使用场景:
- 大型列表渲染
- 条件渲染优化
- 复杂组件的缓存
<template>
<!-- ✅ 正例:v-memo 优化大型列表 -->
<div v-for="item in largeList" :key="item.id" v-memo="[item.id, item.status]">
<ComplexItem :data="item" />
</div>
<!-- ✅ 正例:条件渲染优化 -->
<ExpensiveComponent v-memo="[shouldUpdate]" :data="data" />
</template>5.3 响应式优化
使用 shallowRef 和 shallowReactive:
<script setup>
import { ref, shallowRef, reactive, shallowReactive } from 'vue'
// ✅ 正例:大对象使用 shallowRef
const largeObject = shallowRef({
// 大量数据...
})
// ✅ 正例:只关心表面层级的响应式
const state = shallowReactive({
count: 0,
config: {
// 深层对象不会自动响应
theme: 'dark'
}
})
</script>6. TypeScript 规范(Vue3)
6.1 类型定义规范
组件 Props 类型定义:
<script setup lang="ts">
interface Props {
title: string
count?: number
status: 'active' | 'inactive'
data?: Record<string, any>
}
const props = withDefaults(defineProps<Props>(), {
count: 0,
data: () => ({})
})
</script>Emits 类型定义:
<script setup lang="ts">
interface Emits {
(e: 'update', value: string): void
(e: 'submit', data: SubmitData): void
(e: 'cancel'): void
}
const emit = defineEmits<Emits>()
</script>6.2 Composables 类型定义
// composables/useCounter.ts
import { ref, computed } from 'vue'
interface UseCounterOptions {
initial?: number
min?: number
max?: number
}
interface UseCounterReturn {
count: Ref<number>
doubled: ComputedRef<number>
increment: () => void
decrement: () => void
reset: () => void
}
export function useCounter(options: UseCounterOptions = {}): UseCounterReturn {
const { initial = 0, min = -Infinity, max = Infinity } = options
const count = ref(initial)
const doubled = computed(() => count.value * 2)
const increment = () => {
if (count.value < max) count.value++
}
const decrement = () => {
if (count.value > min) count.value--
}
const reset = () => {
count.value = initial
}
return {
count,
doubled,
increment,
decrement,
reset
}
}7. 代码质量规范
7.1 ESLint 配置
Vue3 推荐配置:
{
"extends": [
"@vue/typescript/recommended",
"@vue/prettier",
"@vue/prettier/@typescript-eslint",
"plugin:vue/vue3-recommended"
],
"rules": {
"vue/multi-word-component-names": "error",
"vue/component-definition-name-casing": ["error", "PascalCase"],
"vue/component-name-in-template-casing": ["error", "PascalCase"],
"vue/custom-event-name-casing": ["error", "camelCase"],
"vue/define-macros-order": "error",
"vue/no-unused-vars": "error",
"vue/padding-line-between-blocks": "error"
}
}7.2 代码审查清单
Vue3 组件审查要点:
- [ ] 组件命名是否符合规范
- [ ] Props 定义是否完整且类型安全
- [ ] 响应式数据是否使用正确(ref vs reactive)
- [ ] 计算属性是否纯函数
- [ ] 监听器是否可能导致性能问题
- [ ] 生命周期钩子是否正确清理资源
- [ ] 组件是否可复用和解耦
- [ ] 是否正确处理异步操作
- [ ] 错误边界是否完善
- [ ] TypeScript 类型是否完整
总结
本规范涵盖了 Vue2 和 Vue3 的编码规范,推荐新项目使用 Vue3 + 组合式 API + TypeScript 的技术栈。开发过程中应该:
- 保持一致性:同一项目中保持编码风格统一
- 注重性能:合理使用 Vue3 的新特性优化性能
- 类型安全:充分利用 TypeScript 的类型保护
- 可维护性:编写易于理解和维护的代码
- 持续改进:根据项目实际需求不断完善规范
