目录

Vue-3-前端工程化规范

Vue 3 前端工程化规范

Vue 3 前端工程化规范

前言

我们遵循以下核心工程原则,这些原则是所有规范的基石:

  • 清晰胜于抖机灵 (Clarity over Cleverness):代码首先是写给人读的,其次才是给机器执行的。优先保证代码的直观和易于理解。
  • 单一职责原则 (Single Responsibility Principle):每个模块、组件或函数都应该只负责一项功能,并把它做好。
  • 自动化优于约定 (Automation over Convention):凡是能通过工具自动保证的规范,就不要依赖于人的记忆和自觉。
  • 小步提交,持续集成 (Small Commits, Continuous Integration):通过小而专注的 Git 提交,简化代码审查,并利用 CI/CD 流水线尽早发现问题。
  • 测试是项目的一部分,不是附属品 (Testing is a Feature):未经测试的代码在设计上就是有缺陷的。单元测试是保证代码质量和未来重构信心的关键。
  • 拥抱类型系统。 TypeScript 不是可选项,而是必需品。它能在编码阶段发现大量潜在错误,并极大地提升代码的可维护性。

我们围绕可维护性、可测试性和团队协作效率等核心理念,整理了这样一份Vue3前端代码规范文档。

1. 文档概述

1.1 文档目的

本文档旨在为基于 Vue 3 技术栈的前端项目提供一套统一的开发标准、最佳实践和协作流程。目标是提升代码的可读性、可维护性、健壮性团队协作效率,确保项目在整个生命周期内保持高质量和技术领先性。

1.2 适用范围

本文档适用于所有参与项目的前端开发工程师、测试工程师、架构师及项目经理。它是代码审查(Code Review)、技术决策和新成员入职培训的核心依据。

2. Vue 3 编码规范

2.1. 函数与变量
2.1.1. 优先使用箭头函数

在 Vue 3 的 <script setup> 组合式 API 场景下,函数通常不依赖 this 上下文,使用箭头函数可以保持代码风格统一。

  • 【推荐】
// 使用 const 和箭头函数定义所有本地函数
const fetchUserData = async (userId) => {
  // ...
};
  • 【反例】
// 风格不统一,降低可读性
function fetchUserData() { /* ... */ }
const updateUser = () => { /* ... */ };
  • 评审要点:

    • 检查是否存在 function 和箭头函数的无理由混用。
    • 方法定义是否一致。
2.1.2. 使用 **let****const**

const 优先,仅在变量需要被重新赋值时使用 let。禁止使用 var 以避免变量提升带来的潜在问题。

  • 【推荐】
const MAX_RETRIES = 3;
let currentUser = await fetchUser();
currentUser = 'guest';
  • 【反例】
var MAX_RETRIES = 3; // 禁止使用 var
  • 评审要点:

    • 代码中是否还存在 var 关键字。
2.2. 数据请求 (异步操作)
2.2.1. 使用 **async/await****try...catch**

async/await 提供了更扁平、更易读的异步代码结构。必须使用 try...catch 块来捕获和处理潜在的 API 错误。

  • 【推荐】
const getTableData = async () => {
  isLoading.value = true;
  try {
    const response = await getTableDataApi({ /* params */ });
    tableData.value = response.data;
  } catch (error) {
    console.error("Failed to fetch table data:", error);
    // 向用户显示错误提示
    showErrorToast('数据加载失败');
  } finally {
    isLoading.value = false;
  }
};
  • 【反例】
// 1. 回调地狱,难以阅读和维护
getTableDataApi().then(res1 => {
  getMoreDataApi(res1.id).then(res2 => {
    // ...
  });
});

// 2. 缺少错误处理
const fetchData = async () => {
  const response = await someApi(); // 如果 someApi() reject,整个函数会崩溃
  data.value = response;
}
  • 评审要点:

    • 所有异步请求是否都被 try...catch 包裹?
    • 是否有清晰的 loading 状态管理?
    • 错误处理是否对用户友好(例如,显示提示)?
    • 是否存在超过一层的 .then() 嵌套?
2.2.2. 合理利用 **Promise.all****Promise.allSettled**

当多个 API 请求没有依赖关系时,应使用 Promise.all 并发执行以缩短总加载时间。

  • 【推荐】
// 场景:页面需要同时加载用户信息和配置信息
const loadPageData = async () => {
  try {
    const [userData, configData] = await Promise.all([
      fetchUserApi(),
      fetchConfigApi(),
    ]);
    user.value = userData;
    config.value = configData;
  } catch (error) {
    // ...
  }
};
  • 评审要点:

    • 是否存在可以用 Promise.all 优化的串行 await 调用?
2.3. 响应式变量
  • 【推荐】 对于需要深度响应的大对象或数组,使用 reactive。对于基本类型或只需替换整个对象的场景,使用 ref
  • 【性能优化】 对于只有顶层属性需要响应性的大型、深层嵌套对象(例如,从后端获取的只读列表),应使用 shallowRefshallowReactive 来避免不必要的性能开销。
2.4. 逻辑分支优化

避免深层嵌套的 if-else 语句,它们会显著增加代码的“圈复杂度”,使其难以理解和测试。

  • 【推荐】使用卫语句 (Guard Clauses) 或映射表 (Map)
// 卫语句(提前返回)
function processPayment(user, order) {
  if (!user) {
    console.error("User not logged in.");
    return;
  }
  if (!order || order.status !== 'pending') {
    showErrorToast("Invalid order.");
    return;
  }
  // ...核心逻辑...
}

// 映射表处理多状态
const statusActions = {
  pending: () => handlePending(),
  shipped: () => handleShipped(),
  delivered: () => handleDelivered(),
  default: () => handleDefault(),
};
const action = statusActions[order.status] || statusActions.default;
action();
  • 【反例】
// 深度嵌套,难以阅读
if (user) {
  if (order) {
    if (order.status === 'pending') {
      // ...核心逻辑...
    } else {
      showErrorToast("Invalid order status.");
    }
  } else {
    showErrorToast("No order found.");
  }
} else {
  console.error("User not logged in.");
}
  • 评审要点:

    • if 语句的嵌套是否超过了2层?
    • 是否存在可以用映射表或策略模式优化的长 if-else if 链?
2.5. 代码整洁度
  • 【推荐】 及时删除被注释掉的代码、调试用的 console.logdebugger 语句。版本控制系统(Git)是你的安全网,无需在代码中保留历史遗迹。
  • 【推荐】 单个文件(特别是Vue组件)的行数应尽量控制在 400行 以内。超过这个长度通常意味着组件承担了过多的职责,需要进行拆分(例如,抽取为子组件或 composable 函数)。

3. 组件与文件命名规范

3.1. 文件命名
  • Vue 组件: 大驼峰 (PascalCase),如 UserProfile.vue
  • Composable 函数: 小驼峰 (camelCase),并以 use 开头,如 useFormValidation.ts
  • 其他 TS/JS 文件: 小驼峰 (camelCase) 或大驼峰 (PascalCase) 均可,但项目内需统一。推荐 小驼峰 (如 apiClient.ts, utils.ts)。
3.2. 组件 **name** 属性

所有组件都必须显式声明一个 name 属性,且与文件名保持一致。这对于 Vue Devtools 调试和组件递归至关重要。

  • 【推荐】

JavaScript

// 在 UserProfile.vue 中
export default defineComponent({
  name: 'UserProfile',
  // ...
});
  • 评审要点:

    • 所有 .vue 文件是否都有一个匹配文件名的 name 属性?

4. Composable (Hooks) 使用规范

Hooks 是 Vue 3 中逻辑复用和功能拆分的核心。

  • 【推荐】将可复用的逻辑(如弹窗控制、数据获取、表单状态)封装成 **composable** 函数。
// composables/useModal.ts
import { ref } from 'vue';

export function useModal() {
  const isVisible = ref(false);
  const openModal = () => isVisible.value = true;
  const closeModal = () => isVisible.value = false;

  return { isVisible, openModal, closeModal };
}

// 在组件中使用
// const { isVisible, openModal, closeModal } = useModal();
  • 【反例】将所有状态和方法都堆砌在一个巨大的 **reactive** 对象中。
// 这种写法将 UI 状态、表单数据和控制方法耦合在一起,难以复用和测试
const editModel = reactive({
  isShow: false,
  form: { name: '...' },
  showFunc: () => { /* ... */ },
  cancelFunc: () => { /* ... */ },
});
  • 评审要点:

    • 组件中是否存在可以被抽象为 composable 的重复逻辑块?
    • composable 是否遵循单一职责原则?

5. 性能优化

  • 路由懒加载: 所有路由必须使用动态导入 (() => import(...)) 实现懒加载。
  • 异步组件: 对于非首屏、体积较大或只在特定条件下渲染的组件(如复杂的弹窗、图表),使用 defineAsyncComponent
  • 善用 **v-once** **v-memo**: 对于纯静态内容,使用 v-once。对于渲染开销大且依赖特定变量的列表,使用 v-memo
  • 虚拟滚动: 对于超过100项的长列表,应采用虚拟滚动技术(可使用 vue-virtual-scroller 等库)。
  • 减少依赖体积: 优先选用 lodash-es 等支持 tree-shaking 的库。定期使用 vite-bundle-analyzer 分析打包产物,移除不必要的依赖。

6. Git 工作流与提交规范

6.1 Git 分支模型

推荐使用简化的 Git-Flow 模型:

  • main: 主分支,用于部署生产环境,代码必须是稳定且经过测试的。只能从 develop 分支合并。
  • develop: 开发主分支,集成了所有已完成的功能。是所有功能分支的父分支。
  • feature/<feature-name>: 功能开发分支,从 develop 创建。命名应清晰,如 feature/user-authentication
  • fix/<fix-name>: Bug 修复分支,从 develop(或紧急情况下从 main)创建。

6.2 Git 提交规范

采用 Conventional Commits 标准,强制通过 commitlint 工具校验。

格式: <type>(<scope>): <subject>

  • 常用 **type**:

    • feat: 新增功能
    • fix: 修复 Bug
    • docs: 仅修改文档
    • style: 代码格式调整(不影响代码逻辑)
    • refactor: 代码重构(既不新增功能,也不修复 Bug)
    • perf: 性能优化
    • test: 新增或修改测试
    • chore: 构建流程、辅助工具的变更
  • 示例:

# 良好示例
feat(user): add user login and registration functionality
fix(form): correct validation logic for email field
refactor(api): abstract api calls into a dedicated service module

7. 项目结构

统一的目录结构是高效协作的基础。推荐以下结构:

/src
├── api/                # API 请求模块 (按业务划分)
│   ├── auth.ts
│   └── user.ts
├── assets/             # 静态资源 (图片, 字体等)
├── components/         # 全局组件
│   ├── base/           # 基础 UI 组件 (BaseButton.vue)
│   └── business/       # 全局业务组件 (UserSelector.vue)
├── composables/        # Vue Composition API 函数 (Hooks)
│   └── usePagination.ts
├── config/             # 全局配置
│   └── index.ts
├── layouts/            # 布局组件
│   └── DefaultLayout.vue
├── router/             # 路由配置
│   └── index.ts
├── stores/             # Pinia 状态管理
│   ├── user.ts
│   └── index.ts
├── styles/             # 全局样式
│   ├── main.scss
│   └── _variables.scss
├── types/              # TypeScript 类型定义
│   └── api.d.ts
├── utils/              # 工具函数
│   └── format.ts
├── views/              # 页面级组件 (路由入口)
│   ├── user/
│   │   ├── UserProfile.vue
│   │   └── UserList.vue
│   └── Home.vue
├── App.vue             # 根组件
└── main.ts             # 应用入口

8. 组件化开发规范

8.1 组件分类标准

明确组件的职责分类,有助于合理规划项目结构和提升复用性。

组件分类职责说明示例存放位置
基础组件 (Base Components)不包含任何业务逻辑,纯粹的 UI 展示和交互。具有极高的复用性,通常带有 BaseApp 前缀。BaseButton.vue, BaseInput.vue, BaseModal.vuesrc/components/base/
业务组件 (Business Components)封装特定业务逻辑,由多个基础组件组合而成。与特定业务场景强相关,可在项目内多处复用。UserCard.vue, OrderHistoryTable.vuesrc/components/business/ 或 页面私有组件 src/views/xxx/components/
页面组件 (View Components)作为路由的入口,负责组织页面布局和业务组件,处理当前页面的数据请求与状态管理。UserProfile.vue, HomePage.vuesrc/views/
布局组件 (Layout Components)定义应用的整体页面结构,如页头、侧边栏、内容区等。DefaultLayout.vue, AdminLayout.vuesrc/layouts/

8.2 组件编写标准

8.2.1 命名规范
  • 文件名: 采用大驼峰命名法 (PascalCase),如 MyComponent.vue
  • 组件 **name** 属性: 必须添加 name 选项,且与文件名保持一致。这对于 Vue Devtools 调试至关重要。
8.2.2 <script setup> 内部顺序

为了保持一致性和可读性,推荐遵循以下顺序:

<script setup lang="ts">
// 1. imports
import { ref, computed } from 'vue';
import ChildComponent from './ChildComponent.vue';

// 2. 类型定义 (Props, Emits, etc.)
interface Props {
  title: string;
  items: string[];
}

// 3. defineProps, defineEmits, defineExpose
const props = withDefaults(defineProps<Props>(), {
  title: 'Default Title',
});
const emit = defineEmits<{
  (e: 'select', item: string): void;
}>();
defineExpose({ reset });

// 4. 响应式状态 (State)
const internalState = ref(0);

// 5. 计算属性 (Computed Properties)
const formattedTitle = computed(() => `--- ${props.title} ---`);

// 6. 侦听器 (Watchers)
watch(() => props.items, (newVal) => {
  // ...
});

// 7. 生命周期钩子 (Lifecycle Hooks)
onMounted(() => {
  // ...
});

// 8. 方法 (Methods)
function handleClick() {
  emit('select', 'some-item');
}

function reset() {
  internalState.value = 0;
}
</script>
8.2.3 Props & Emits
  • 必须使用 TypeScript 进行类型定义,以获得最终的类型安全。
  • props 提供合理的默认值 (withDefaults)。
  • 事件 (emits) 必须明确定义其名称和载荷类型。
// 【推荐】清晰、类型安全
interface Props {
  user: User;
  isVisible?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
  isVisible: false,
});

const emit = defineEmits<{
  (e: 'update:isVisible', value: boolean): void;
  (e: 'submit', data: User): void;
}>();

// 【反例】类型不明确,难以维护
defineProps(['user', 'isVisible']);
defineEmits(['update:isVisible', 'submit']);
8.2.4 样式规范
  • 强制使用 **<style scoped>** 避免全局样式污染。
  • 若需修改子组件样式,优先使用 CSS 变量。万不得已时,使用 :deep() 选择器,但需谨慎。

8.3 组件通信标准

根据场景选择最合适的通信方式。

通信场景推荐方式优点缺点/注意事项
父 -> 子Props单向数据流,清晰易懂,性能好。仅限直系父子。
子 -> 父Emits标准事件模式,解耦。仅限直系父子。
祖先 -> 后代Provide / Inject避免 Props 逐层传递(Prop Drilling)。数据来源不直观,可能导致耦合。
兄弟或任意组件Pinia (状态管理)集中式状态管理,数据流可预测,Devtools 支持强大。增加了少量模板代码。

【反例】禁止使用 Event Bus (如 mitt.js) 虽然 Event Bus 能实现任意组件通信,但它会导致数据流混乱,难以追踪和调试,在大型项目中是灾难性的。Pinia 是其完美的替代方案。

9. 单元测试规范

单元测试是代码质量的守护神。我们使用 Vitest 作为测试框架,Vue Test Utils 作为组件测试工具库。

9.1 测试标准

  • 公共组件、Hooks 函数、工具函数必须编写单元测试。
  • 业务组件应测试其核心交互逻辑。
  • 测试覆盖率目标:核心模块 > 90%,项目平均 > 80%。

9.2 编写规范

  • 测试文件名: *.spec.ts*.test.ts

  • 测试关注点: 测试组件的“公共契约”,即 Props、Events 和 Slots,而不是内部实现细节。

  • 遵循 AAA 模式:

    • Arrange (安排): 准备测试环境,如挂载组件、设置 props。
    • Act (行动): 执行操作,如触发点击事件、修改输入框。
    • Assert (断言): 验证结果是否符合预期,如检查 DOM 变化、emit 事件。

9.3 示例

测试一个基础按钮组件 (BaseButton.spec.ts)
// 【正例】
import { mount } from '@vue/test-utils';
import { describe, it, expect } from 'vitest';
import BaseButton from '../BaseButton.vue';

describe('BaseButton.vue', () => {
  // 测试点1:渲染
  it('should render the slot content', () => {
    // Arrange
    const wrapper = mount(BaseButton, {
      slots: {
        default: 'Click Me',
      },
    });
    // Assert
    expect(wrapper.text()).toBe('Click Me');
  });

  // 测试点2:交互 (emit)
  it('should emit a click event when clicked', async () => {
    // Arrange
    const wrapper = mount(BaseButton);
    // Act
    await wrapper.trigger('click');
    // Assert
    expect(wrapper.emitted()).toHaveProperty('click');
    expect(wrapper.emitted('click')).toHaveLength(1);
  });

  // 测试点3:属性 (props)
  it('should be disabled when the disabled prop is true', async () => {
    // Arrange
    const wrapper = mount(BaseButton, {
      props: {
        disabled: true,
      },
    });
    // Assert
    expect(wrapper.attributes('disabled')).toBeDefined();
    // Act
    await wrapper.trigger('click');
    // Assert
    expect(wrapper.emitted('click')).toBeUndefined();
  });
});
// 【反例】测试内部实现
it('should have a specific internal class when hovered', () => {
  // 这是一个糟糕的测试,因为它依赖于组件的内部样式实现,
  // 一旦样式类名改变,测试就会失败,即使组件功能完好。
});

10. 代码审查 (Code Review) 标准

Code Review (CR) 是保证代码质量、促进知识共享的关键环节。

10.1 CR 要点 (Checklist)

审查者应重点关注以下方面:

  1. 功能性 (Functionality): 代码是否正确地实现了需求?是否考虑了边界情况?
  2. 可读性 (Readability): 变量和函数命名是否清晰?逻辑是否易于理解?是否存在过于复杂的代码?
  3. 规范性 (Consistency): 是否遵循了本文档中定义的所有规范?
  4. 可维护性 (Maintainability): 代码是否易于修改和扩展?是否存在硬编码或“魔法数字”?
  5. 性能 (Performance): 是否存在明显的性能问题?(如不必要的 watch deep、过大的 VNode 渲染)
  6. 测试 (Testing): 是否包含了必要的单元测试?测试用例是否有效且覆盖了关键逻辑?
  7. 注释 (Comments): 复杂的逻辑或“为什么”这么做的原因是否添加了注释?

11. 自动化与工程化

11.1 工具链

  • 包管理器: pnpm (推荐,速度快且节省磁盘空间)
  • 构建工具: Vite
  • 代码规范: ESLint (集成 vue, typescript 插件)
  • 代码格式化: Prettier (与 ESLint 集成,解决格式冲突)
  • Git 钩子: Husky + lint-staged
  • Commit 规范: @commitlint/cli

Vue 3 前端工程化规范

11.2 自动化流程

11.2.1 本地开发 (Pre-commit)

通过 huskylint-staged 配置 pre-commit 钩子,在代码提交前自动执行:

  1. 格式化: prettier --write 对暂存区的文件进行格式化。
  2. 代码检查: eslint --fix 对暂存区的文件进行语法和规范检查。
  3. (可选)单元测试: vitest related --run 仅运行与修改文件相关的测试。

**package.json** 配置示例:

{
  "scripts": {
    "prepare": "husky install"
  },
  "lint-staged": {
    "*.{js,ts,vue}": ["eslint --fix", "prettier --write"],
    "*.{scss,css}": ["stylelint --fix"]
  }
}
11.2.2 持续集成 (CI/CD)

GitHub Actions 为例,配置工作流,在提交 Pull Request 时自动触发。

**.github/workflows/ci.yml**:

name: Frontend CI

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main, develop ]

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Setup pnpm
        uses: pnpm/action-setup@v2
        with:
          version: 8

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'pnpm'

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Lint and Format Check
        run: pnpm lint

      - name: Run Unit Tests
        run: pnpm test:run --coverage # 运行测试并生成覆盖率报告

      - name: Build Project
        run: pnpm build

      # 可选:上传覆盖率报告到 Codecov 等服务
      # - name: Upload coverage reports to Codecov
      #   uses: codecov/codecov-action@v3

这个 CI 流程确保了每一份合入 developmain 分支的代码都经过了严格的自动化校验,极大地保障了主干分支的健康度。