前端组知识库

👍欢迎大家积极投稿交流👍

如果文档内容陈旧或者链接失效,请发现后及时同步,我将尽快修改

👇微信👇

图片
Skip to content

Vue规范

1. 命名规范

1.1 文件命名

  • 文件名统一使用小写字母,多个单词之间使用连字符(-)分隔,如:user-profile.vueapi-request.js
  • 组件文件命名采用大驼峰命名法(PascalCase),如:UserProfile.vueTodoList.vue

1.2 变量命名

  • 变量名使用小驼峰命名法(camelCase),如:userNameisLoading
  • 常量使用全大写字母,多个单词之间使用下划线分隔,如:MAX_COUNTAPI_BASE_URL
  • 布尔值变量使用 is、has、can 等前缀,如:isVisiblehasPermission

1.3 函数命名

  • 函数名使用小驼峰命名法(camelCase)
  • 函数名应具有语义化,能够清晰表达函数的功能,如:getUserInfo()formatDate()

2. 缩进规范

  • 统一使用 2 个空格进行缩进,不要使用 Tab 键
  • 在 VS Code 中可以通过设置 "editor.tabSize": 2"editor.insertSpaces": true 来保持一致

3. 换行规范

  • 每行代码长度不应超过 100 个字符
  • 逗号后必须添加空格,如:const arr = [1, 2, 3]
  • 对象属性、数组元素等应适当换行以提高可读性:
javascript
// 好的示例
const user = {
  id: 1,
  name: 'John',
  email: 'john@example.com',
  age: 25
}

// 数组换行示例
const fruits = [
  'apple',
  'banana',
  'orange',
  'grape'
]

4. Vue 开发规范(基于 Vue 3)

4.1 组件结构

  • 组件选项应按照以下顺序排列:
    1. name
    2. components
    3. props
    4. emits
    5. setupdatacomputedwatch
    6. 生命周期钩子
    7. methods

4.2 组件设计原则

  • 单一职责:每个组件只负责一个明确的功能
  • 可复用性:组件应设计为可在不同场景下复用
  • 可维护性:组件代码应清晰、简洁,易于理解和维护
  • 可测试性:组件应便于进行单元测试

4.3 Props 规范

  • Props 验证:必须为所有 Props 添加类型验证
  • 默认值:为可选 Props 提供合理的默认值
  • 命名规范:Props 名称使用小驼峰命名法,在模板中使用 kebab-case
  • 文档:为复杂 Props 添加注释说明
vue
// 好的示例
const props = defineProps({
  /**
   * 用户信息对象
   */
  userInfo: {
    type: Object,
    required: true
  },
  /**
   * 是否显示加载状态
   */
  loading: {
    type: Boolean,
    default: false
  },
  /**
   * 分页大小
   */
  pageSize: {
    type: Number,
    default: 10,
    validator: (value) => value > 0
  }
})

4.4 Emits 规范

  • 事件命名:事件名使用 kebab-case
  • 事件文档:为事件添加注释说明
  • 事件参数:事件参数应简洁明了,避免传递过多数据
vue
// 好的示例
const emit = defineEmits({
  /**
   * 更新用户信息事件
   * @param {Object} userInfo - 更新后的用户信息
   */
  'update:userInfo': (userInfo) => typeof userInfo === 'object',
  /**
   * 点击事件
   * @param {MouseEvent} event - 鼠标事件对象
   */
  'click': (event) => event instanceof MouseEvent
})

4.5 模板规范

  • 模板中的组件名应使用帕斯卡命名法(PascalCase)
  • 组件属性应按重要性排序,使用缩进保持层次清晰
  • 指令缩写使用 : 表示 v-bind@ 表示 v-on
  • 避免在模板中使用复杂的表达式,应将逻辑移至脚本部分
  • 使用 v-ifv-for 时应注意优先级,避免同时使用
vue
<template>
  <UserProfile 
    :user-info="userInfo" 
    :loading="loading"
    @update:userInfo="handleUpdate"
    @click="handleClick"
  />
</template>

4.6 Composition API 规范

  • 使用 <script setup> 语法糖简化组件编写
  • 逻辑相关的代码应组织在一起,使用注释分隔不同功能模块
  • 响应式数据使用 refreactive 合理区分:
    • ref:用于基本类型和需要在模板中直接使用的响应式数据
    • reactive:用于复杂对象的响应式数据
  • 使用 computed 处理派生状态,避免在模板中进行复杂计算
  • 使用 watchwatchEffect 监听数据变化,注意清理副作用
  • 自定义 Composables 应放在单独的文件中,以 use 开头命名

4.7 组件通信规范

  • Props/Emits:父子组件通信的首选方式
  • Provide/Inject:跨层级组件通信的方式
  • Pinia/Vuex:全局状态管理
  • Event Bus:非父子组件间的简单通信(应谨慎使用)
  • Slots:组件内容分发

4.8 组件样式规范

  • Scoped 样式:组件样式应使用 scoped 修饰符避免样式污染
  • CSS Modules:对于需要在 JavaScript 中访问的样式,使用 CSS Modules
  • 动态样式:使用 :class:style 绑定动态样式
  • 样式隔离:避免使用 !important 覆盖样式

4.9 响应式数据管理规范

4.9.1 ref vs reactive

  • ref

    • 用于基本类型(字符串、数字、布尔值等)
    • 用于需要在模板中直接使用的响应式数据
    • 需要通过 .value 访问和修改值
    • 适合单个独立的值
  • reactive

    • 用于复杂对象和数组
    • 直接访问和修改属性,不需要 .value
    • 不适合基本类型(会失去响应性)
    • 适合相关数据的集合
vue
// 好的示例
import { ref, reactive } from 'vue'

// 使用 ref 管理基本类型
const count = ref(0)
const loading = ref(false)
const message = ref('Hello')

// 使用 reactive 管理复杂对象
const user = reactive({
  name: 'John',
  age: 25,
  address: {
    city: 'Beijing',
    district: 'Haidian'
  }
})

// 修改值
count.value++
user.name = 'Tom'

4.9.2 computed 规范

  • 使用场景:处理派生状态,避免在模板中进行复杂计算
  • 缓存机制:computed 属性会自动缓存,只有依赖变化时才会重新计算
  • 可读性:为复杂的 computed 属性添加注释说明
  • 性能:避免在 computed 中执行异步操作或副作用
vue
// 好的示例
import { ref, computed } from 'vue'

const items = ref([1, 2, 3, 4, 5])

/**
 * 计算偶数的数量
 */
const evenCount = computed(() => {
  return items.value.filter(item => item % 2 === 0).length
})

/**
 * 计算总和
 */
const total = computed(() => {
  return items.value.reduce((sum, item) => sum + item, 0)
})

4.9.3 watch 规范

  • 使用场景:监听数据变化并执行副作用
  • 清理函数:在 watch 中执行异步操作时,必须返回清理函数
  • 深度监听:谨慎使用深度监听,可能影响性能
  • 即时执行:使用 immediate 选项在初始时执行一次
vue
// 好的示例
import { ref, watch } from 'vue'

const searchQuery = ref('')
const searchResults = ref([])

// 监听搜索查询变化
watch(searchQuery, (newQuery, oldQuery) => {
  // 清理函数
  let timeoutId
  
  if (newQuery) {
    timeoutId = setTimeout(() => {
      // 模拟异步搜索
      searchApi(newQuery).then(results => {
        searchResults.value = results
      })
    }, 300)
  } else {
    searchResults.value = []
  }
  
  // 返回清理函数
  return () => {
    if (timeoutId) clearTimeout(timeoutId)
  }
})

// 深度监听对象变化
const user = ref({
  name: 'John',
  address: {
    city: 'Beijing'
  }
})

watch(
  user,
  (newUser) => {
    console.log('User changed:', newUser)
  },
  { deep: true }
)

4.9.4 watchEffect 规范

  • 使用场景:自动追踪所有依赖的响应式数据
  • 清理函数:返回清理函数以避免内存泄漏
  • 执行时机:组件挂载时立即执行一次,然后响应依赖变化
  • 注意事项:只在需要自动追踪多个依赖时使用
vue
// 好的示例
import { ref, watchEffect } from 'vue'

const width = ref(0)
const height = ref(0)

// 监听窗口大小变化
watchEffect(() => {
  const handleResize = () => {
    width.value = window.innerWidth
    height.value = window.innerHeight
  }
  
  window.addEventListener('resize', handleResize)
  handleResize() // 立即执行一次
  
  // 返回清理函数
  return () => {
    window.removeEventListener('resize', handleResize)
  }
})

5. 项目结构规范

5.1 目录结构

  • src/:源代码目录
    • assets/:静态资源(图片、字体等)
    • components/:通用组件
    • composables/:自定义 Composables
    • directives/:自定义指令
    • layouts/:布局组件
    • pages/:页面组件
    • router/:路由配置
    • stores/:状态管理(Pinia/Vuex)
    • services/:API 服务
    • utils/:工具函数
    • styles/:全局样式
    • types/:TypeScript 类型定义
    • App.vue:根组件
    • main.ts:入口文件

5.2 文件命名规范

  • 组件文件:使用 PascalCase 命名,如 UserProfile.vue
  • 非组件文件:使用 kebab-case 命名,如 api-client.js
  • 工具函数文件:使用 kebab-case 命名,如 date-utils.js
  • 类型定义文件:使用 PascalCase 命名,如 UserType.ts

5.3 组织原则

  • 按功能组织:相关文件放在同一目录下
  • 单一职责:每个文件只负责一个功能
  • 可维护性:目录结构清晰,易于导航和理解
  • 可扩展性:预留扩展空间,便于添加新功能

6. Vue Router 规范

6.1 路由配置

  • 使用 TypeScript:为路由配置添加类型定义
  • 路由命名:路由名称使用 PascalCase
  • 路径命名:路由路径使用 kebab-case
  • 懒加载:使用动态导入实现路由懒加载,提高首屏加载速度
  • 路由元信息:为路由添加元信息,如标题、权限等
typescript
// 好的示例
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/',
      name: 'Home',
      component: () => import('@/pages/Home.vue'),
      meta: {
        title: '首页',
        requiresAuth: false
      }
    },
    {
      path: '/user/:id',
      name: 'UserDetail',
      component: () => import('@/pages/UserDetail.vue'),
      props: true,
      meta: {
        title: '用户详情',
        requiresAuth: true
      }
    }
  ]
})

6.2 导航守卫

  • 全局守卫:处理全局路由逻辑,如登录验证
  • 路由守卫:处理特定路由的逻辑
  • 组件守卫:处理组件级别的路由逻辑
  • 清理:在守卫中执行异步操作时,注意处理取消逻辑
typescript
// 好的示例
// 全局前置守卫
router.beforeEach((to, from, next) => {
  // 设置页面标题
  document.title = to.meta.title || 'App'
  
  // 登录验证
  const requiresAuth = to.meta.requiresAuth
  const isLoggedIn = localStorage.getItem('token')
  
  if (requiresAuth && !isLoggedIn) {
    next('/login')
  } else {
    next()
  }
})

// 全局后置守卫
router.afterEach((to, from) => {
  // 记录路由跳转
  console.log(`Navigated from ${from.path} to ${to.path}`)
})

6.3 路由使用

  • 编程式导航:使用 router.push()router.replace() 进行编程式导航
  • 路由参数:使用 useRoute() 获取路由参数
  • 路由状态:避免在路由参数中传递复杂状态,使用 Pinia/Vuex 管理
vue
// 好的示例
import { useRouter, useRoute } from 'vue-router'

const router = useRouter()
const route = useRoute()

// 编程式导航
const navigateToUser = (userId) => {
  router.push({
    name: 'UserDetail',
    params: { id: userId }
  })
}

// 获取路由参数
const userId = route.params.id

7. 状态管理规范(Pinia)

7.1 Store 设计

  • 模块化:按功能划分 Store,避免单一巨大的 Store
  • 命名规范:Store 名称使用 PascalCase
  • 文件命名:Store 文件使用 kebab-case 命名,如 user-store.ts
  • 状态结构:状态结构清晰,避免嵌套过深

7.2 State 规范

  • 初始化:为所有状态提供合理的初始值
  • 类型定义:使用 TypeScript 为 State 添加类型定义
  • 不可变性:虽然 Pinia 允许直接修改状态,但建议使用 actions 修改复杂状态

7.3 Getters 规范

  • 使用场景:处理派生状态,类似于组件中的 computed
  • 缓存机制:Getters 会自动缓存,提高性能
  • 可读性:为复杂的 Getters 添加注释说明

7.4 Actions 规范

  • 异步操作:处理异步逻辑,如 API 调用
  • 状态修改:修改状态的逻辑应放在 actions 中
  • 错误处理:在 actions 中添加错误处理
  • 返回值:为异步 actions 返回 Promise,便于组件处理
typescript
// 好的示例
import { defineStore } from 'pinia'
import { userApi } from '@/services/api'

interface User {
  id: number
  name: string
  email: string
}

interface UserState {
  users: User[]
  loading: boolean
  error: string | null
}

export const useUserStore = defineStore('user', {
  state: (): UserState => ({
    users: [],
    loading: false,
    error: null
  }),
  
  getters: {
    /**
     * 获取活跃用户数量
     */
    activeUserCount: (state) => {
      return state.users.length
    },
    
    /**
     * 根据 ID 获取用户
     */
    getUserById: (state) => (id: number) => {
      return state.users.find(user => user.id === id)
    }
  },
  
  actions: {
    /**
     * 获取用户列表
     */
    async fetchUsers() {
      this.loading = true
      this.error = null
      
      try {
        const response = await userApi.getUsers()
        this.users = response.data
      } catch (error) {
        this.error = 'Failed to fetch users'
        console.error(error)
      } finally {
        this.loading = false
      }
    },
    
    /**
     * 添加用户
     */
    async addUser(user: Omit<User, 'id'>) {
      this.loading = true
      this.error = null
      
      try {
        const response = await userApi.createUser(user)
        this.users.push(response.data)
        return response.data
      } catch (error) {
        this.error = 'Failed to add user'
        console.error(error)
        throw error
      } finally {
        this.loading = false
      }
    }
  }
})

8. 性能优化规范

8.1 组件优化

  • v-memo:使用 v-memo 指令缓存模板片段
  • v-once:对于静态内容使用 v-once 指令
  • v-memo:使用 v-memo 指令缓存计算结果
  • 虚拟滚动:对于长列表使用虚拟滚动技术
  • 组件拆分:将大型组件拆分为 smaller、可复用的组件
  • 避免不必要的重渲染:使用 shallowRefshallowReactive 处理不需要深度响应的数据

8.2 资源优化

  • 图片优化:使用适当的图片格式和大小,考虑使用 WebP 格式
  • 代码分割:使用动态导入实现代码分割
  • Tree Shaking:使用 ES 模块,避免导入未使用的代码
  • CDN 加速:对于第三方库使用 CDN 加速

8.3 网络优化

  • 缓存策略:合理使用浏览器缓存和 HTTP 缓存
  • 请求合并:合并多个 API 请求,减少网络往返
  • 分页加载:对于大量数据使用分页加载
  • 预加载:使用 link rel="preload" 预加载关键资源

8.4 构建优化

  • 生产环境构建:使用 npm run build 进行生产环境构建
  • Source Maps:在生产环境中禁用 Source Maps
  • 压缩:启用代码压缩和图片压缩
  • 分析构建结果:使用 vue-cli-service build --report 分析构建结果

9. 测试规范

9.1 测试类型

  • 单元测试:测试单个组件、函数或模块
  • 集成测试:测试组件之间的交互
  • E2E 测试:测试完整的用户流程

9.2 测试工具

  • Vitest:Vue 3 官方推荐的单元测试框架
  • Cypress:E2E 测试框架
  • @vue/test-utils:Vue 组件测试工具库

9.3 测试编写规范

  • 测试文件命名:测试文件与被测试文件同名,添加 .spec.test 后缀
  • 测试覆盖:重点测试核心功能和关键路径
  • 测试可读性:测试用例描述清晰,便于理解
  • 测试独立性:测试用例之间应相互独立,避免依赖
typescript
// 好的示例
import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import UserProfile from '@/components/UserProfile.vue'

describe('UserProfile.vue', () => {
  it('should render user information', () => {
    const userInfo = {
      name: 'John',
      email: 'john@example.com'
    }
    
    const wrapper = mount(UserProfile, {
      props: {
        userInfo
      }
    })
    
    expect(wrapper.text()).toContain('John')
    expect(wrapper.text()).toContain('john@example.com')
  })
  
  it('should emit update event when edit button is clicked', async () => {
    const userInfo = {
      name: 'John',
      email: 'john@example.com'
    }
    
    const wrapper = mount(UserProfile, {
      props: {
        userInfo
      }
    })
    
    await wrapper.find('button.edit-button').trigger('click')
    
    expect(wrapper.emitted('update:userInfo')).toBeTruthy()
  })
})

10. 错误处理规范

10.1 错误捕获

  • 全局错误处理:使用 app.config.errorHandler 捕获全局错误
  • 组件错误处理:使用 errorCaptured 生命周期钩子捕获组件错误
  • 异步错误处理:使用 try/catch 捕获异步操作错误

10.2 错误边界

  • 创建错误边界组件:捕获子组件树中的错误
  • 错误提示:向用户显示友好的错误提示
  • 错误日志:将错误信息发送到监控系统

10.3 API 错误处理

  • 统一错误处理:创建统一的 API 错误处理函数
  • 错误提示:根据错误类型显示不同的错误提示
  • 重试机制:对于网络错误,提供重试机制

11. 代码审查规范

11.1 审查重点

  • 代码质量:代码是否清晰、简洁、易于理解
  • 性能:是否存在性能问题
  • 安全性:是否存在安全漏洞
  • 可维护性:代码是否易于维护和扩展
  • 规范遵循:是否遵循项目规范

11.2 审查流程

  • 提交前检查:使用 lint 工具检查代码
  • 代码审查:团队成员相互审查代码
  • 持续集成:在 CI 流程中运行测试和 lint

11.3 审查工具

  • ESLint:检查 JavaScript/TypeScript 代码质量
  • Prettier:格式化代码
  • TypeScript:类型检查
  • Vue ESLint Plugin:检查 Vue 特定的代码质量问题

12. React 开发规范

12.1 组件规范

  • 函数组件优先于类组件
  • 组件文件应默认导出组件本身
  • 组件属性应使用 TypeScript 接口定义类型

12.2 Hooks 规范

  • 自定义 Hooks 应以 use 开头命名
  • Hooks 应在组件顶层调用,不能在条件语句中使用
  • 相关的状态和方法应组织在一起

上次更新于: