Skip to content

Vue 项目规范

本规范涵盖 Vue2 和 Vue3 的编码规范,以 Vue 官方风格指南Vue3 官方文档 为基础制定。

版本支持说明

  • Vue2:选项式 API(Options API)
  • Vue3:组合式 API(Composition API)和选项式 API
  • 推荐:新项目使用 Vue3 + 组合式 API;现有 Vue2 项目可逐步迁移到 Vue3

1. Vue 编码基础

所有 Vue 项目均需遵循以下基础规范。

1. 1 组件规范

  1. 组件名为多个单词。 组件名应该始终是多个单词组成(大于等于 2),且命名规范为KebabCase格式。这样做可以避免跟现有的以及未来的 HTML 元素相冲突,因为所有的 HTML 元素名称都是单个单词的。
js
// 正例
export default {
    name: 'TodoItem'
    // ...
};
// 反例
export default {
  name: 'Todo',
  // ...
}
export default {
  name: 'todo-item',
  // ...
}
  1. 组件文件名为pascal-case 格式
// 正例
components/
    |- my-component.vue
// 反例
components/
    |- myComponent.vue
    |- MyComponent.vue
  1. 基础组件文件名为 base 开头,使用完整单词而不是缩写。
// 正例
components/
    |- base-button.vue
    |- base-table.vue
    |- base-icon.vue
// 反例
components/
    |- MyButton.vue
    |- VueTable.vue
    |- Icon.vue
  1. 和父组件紧密耦合的子组件应该以父组件名作为前缀命名
// 正例
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 (使用了缩写)
  1. 在 Template 模版中使用组件,应使用PascalCase模式,并且使用自闭合组件。
// 正例
<!-- 在单文件组件、字符串模板和 JSX 中 -->
<MyComponent />
<Row><Table :column="data"/></Row>
// 反例
<my-component /> <row><table :column="data"/></row>
  1. 组件的 data 必须是一个函数
js
// 正例
export default {
  data () {
    return {
      name: 'jack'
    }
  }
}
// 反例
export default {
  data: {
    name: 'jack'
  }
}
  1. Prop 定义应该尽量详细
  • 必须使用 camelCase 驼峰命名
  • 必须指定类型
  • 必须加上注释,表明其含义
  • 必须加上 required 或者 default,两者二选其一
  • 如果有业务需要,必须加上 validator 验证
js
export default {
  props: {
    // 组件状态,用于控制组件的颜色
    status: {
      type: String,
      required: true,
      validator: function (value) {
        return [
          'succ',
          'info',
          'error'
        ].indexOf(value) !== -1
      }
    },
    // 用户级别,用于显示皇冠个数
    userLevel: {
        type: String,
        required: true
    }
  }
}
  1. 为组件样式设置作用域
vue
<template>
  <button class="btn btn-close">X</button>
</template>
<!-- 使用 `scoped` 特性 -->
<style scoped>
  .btn-close {
    background-color: red;
  }
</style>
  1. 如果特性元素较多,应该主动换行。
vue
<template>
  <button
    class="btn btn-close"
    id="myButton">
    X
  </button>
</template>

1.2 模板中使用简单的表达式

组件模板应该只包含简单的表达式,复杂的表达式则应该重构为计算属性或方法。复杂表达式会让你的模板变得不那么声明式。我们应该尽量描述应该出现的是什么,而非如何计算那个值。而且计算属性和方法使得代码可以重用。

vue
// 正例
<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:)

vue
// 正例
<input
  @input="onInput"
  @focus="onFocus"
>
// 反例
<input
  v-on:input="onInput"
  @focus="onFocus"
>

1.4 标签顺序保持一致

单文件组件应该总是让标签顺序保持为

vue
<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

js
export default {
  name: 'MyComponent',
  components: { ... },
  props: { ... },
  data() { ... },
  computed: { ... },
  watch: { ... },
  filters: { ... },
  beforeCreate() { ... },
  created() { ... },
  beforeMount() { ... },
  mounted() { ... },
  beforeUpdate() { ... },
  updated() { ... },
  beforeDestroy() { ... },
  destroyed() { ... },
  methods: { ... }
}

Vue3 组合式 API 建议顺序:

  1. 导入声明
  2. Props 定义
  3. Emits 定义
  4. 响应式数据(ref, reactive)
  5. 计算属性(computed)
  6. 监听器(watch)
  7. 生命周期钩子
  8. 方法定义
  9. 暴露内容
vue
<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 规范

  1. 页面跳转数据传递使用路由参数 页面跳转,例如 A 页面跳转到 B 页面,需要将 A 页面的数据传递到 B 页面,推荐使用路由参数进行传参,而不是将需要传递的数据保存 vuex,然后在 B 页面取出 vuex 的数据,因为如果在 B 页面刷新会导致 vuex 数据丢失,导致 B 页面无法正常显示数据。
js
let id = ' 123';
this.$router.push({ name: 'userCenter', query: { id: id } });
  1. 使用路由懒加载(延迟加载)机制
js
{
    path: '/uploadAttachment',
    name: 'uploadAttachment',
    meta: {
      title: '上传附件'
    },
    component: () => import('@/view/components/uploadAttachment/index.vue')
},
  1. router 中的命名规范
  • path、childrenPoints 命名规范采用kebab-case命名规范(尽量vue文件的目录结构保持一致,因为目录、文件名都是kebab-case,这样很方便找到对应的文件)
  • name 命名规范采用KebabCase命名规范且和component组件名保持一致!(因为要保持keep-alive特性,keep-alive按照component的name进行缓存,所以两者必须高度保持一致)
js
// 动态加载
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')
      }
    ]
  }
];
  1. router 中的 path 命名规范 path除了采用kebab-case命名规范以外,必须以 / 开头,即使是children里的path也要以 / 开头。如下示例 目的: 经常有这样的场景:某个页面有问题,要立刻找到这个vue文件,如果不用以/开头,path为parent和children组成的,可能经常需要在router文件里搜索多次才能找到,而如果以/开头,则能立刻搜索到对应的组件;
js
{
    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 响应式数据使用规范

使用 refreactive 的场景:

  • 基本类型数据使用 ref
  • 对象类型数据优先使用 reactive,复杂场景使用 ref
  • 需要整体替换对象时使用 ref
vue
<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 开头或具有明确语义
  • 避免副作用,只做计算
vue
<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
vue
<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 → 组件卸载后
vue
<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 数据
vue
<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 开头
  • 语义明确,功能单一
  • 返回响应式数据或方法
ts
// 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.vue

2.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模块
  1. 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)
},
  1. assets 目录 assets 为静态资源,里面存放 images, styles, icons 等静态资源,静态资源命名格式为kebab-case
|assets
|-- icons
|-- images
|   |-- background-color.png
|   |-- upload-header.png
|-- styles
  1. components 目录 此目录应按照组件进行目录划分,目录命名为 kebab-case,组件命名规则也为 kebab-case
|components
|-- markdown-editor
|   |-- index.vue
|   |-- index.js
|   |-- index.less
  1. constants 目录 此目录存放项目所有常量,如果常量在 vue 中使用,请使用 vue-enum 插件(https://www.npmjs.com/package/vue-enum) 目录结构:
|constants
|-- index.js
|-- role.js
|-- employee.js

employee.js

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 时推荐模块化导入
ts
// 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
ts
// 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 = []
    }
  }
})
  1. 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 其他

  1. 尽量不要手动操作 DOM 因使用 vue 框架,所以在项目开发中尽量使用 vue 的数据驱动更新 DOM,尽量(不到万不得已)不要手动操作 DOM,包括:增删改 dom 元素、以及更改样式、添加事件等。

4. Vue3 特有规范

4.1 Teleport 使用规范

使用场景:

  • 模态框、弹窗等需要脱离组件层级结构的场景
  • 全局提示消息
  • 下拉菜单等需要避免 z-index 问题的组件
vue
<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 用于异步组件加载:

  • 提供加载中状态
  • 错误边界处理
  • 异步组件的优雅降级
vue
<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 规范

使用场景:

  • 跨层级组件通信
  • 主题配置
  • 全局配置传递
vue
<!-- 父组件 -->
<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 组件懒加载

路由懒加载:

ts
// ✅ 正例:路由懒加载
const routes = [
  {
    path: '/user',
    component: () => import('@/views/user/index.vue')
  }
]

组件懒加载:

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 优化

使用场景:

  • 大型列表渲染
  • 条件渲染优化
  • 复杂组件的缓存
vue
<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 响应式优化

使用 shallowRefshallowReactive

vue
<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 类型定义:

vue
<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 类型定义:

vue
<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 类型定义

ts
// 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 推荐配置:

json
{
  "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 的技术栈。开发过程中应该:

  1. 保持一致性:同一项目中保持编码风格统一
  2. 注重性能:合理使用 Vue3 的新特性优化性能
  3. 类型安全:充分利用 TypeScript 的类型保护
  4. 可维护性:编写易于理解和维护的代码
  5. 持续改进:根据项目实际需求不断完善规范