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 上下文,使用箭头函数可以保持代码风格统一。

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

    • 检查是否存在 function 和箭头函数的无理由混用。

    • 方法定义是否一致。

2.1.2. 使用 **let****const**

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

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

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

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

  • 【推荐】
plaintext 复制代码
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;
  }
};
  • 【反例】
plaintext 复制代码
// 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 并发执行以缩短总加载时间。

  • 【推荐】
plaintext 复制代码
// 场景:页面需要同时加载用户信息和配置信息
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)
plaintext 复制代码
// 卫语句(提前返回)
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();
  • 【反例】
plaintext 复制代码
// 深度嵌套,难以阅读
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

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

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

4. Composable (Hooks) 使用规范

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

  • 【推荐】将可复用的逻辑(如弹窗控制、数据获取、表单状态)封装成 **composable** 函数。
plaintext 复制代码
// 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** 对象中。
plaintext 复制代码
// 这种写法将 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: 构建流程、辅助工具的变更

  • 示例:

plaintext 复制代码
# 良好示例
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. 项目结构

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

plaintext 复制代码
/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.vue src/components/base/
业务组件 (Business Components) 封装特定业务逻辑,由多个基础组件组合而成。与特定业务场景强相关,可在项目内多处复用。 UserCard.vue, OrderHistoryTable.vue src/components/business/ 或 页面私有组件 src/views/xxx/components/
页面组件 (View Components) 作为路由的入口,负责组织页面布局和业务组件,处理当前页面的数据请求与状态管理。 UserProfile.vue, HomePage.vue src/views/
布局组件 (Layout Components) 定义应用的整体页面结构,如页头、侧边栏、内容区等。 DefaultLayout.vue, AdminLayout.vue src/layouts/

8.2 组件编写标准

8.2.1 命名规范
  • 文件名 : 采用大驼峰命名法 (PascalCase),如 MyComponent.vue

  • 组件 **name** 属性 : 必须添加 name 选项,且与文件名保持一致。这对于 Vue Devtools 调试至关重要。

8.2.2 <script setup> 内部顺序

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

plaintext 复制代码
<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) 必须明确定义其名称和载荷类型。

plaintext 复制代码
// 【推荐】清晰、类型安全
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)
plaintext 复制代码
// 【正例】
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();
  });
});
plaintext 复制代码
// 【反例】测试内部实现
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** 配置示例:

plaintext 复制代码
{
  "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**:

plaintext 复制代码
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 分支的代码都经过了严格的自动化校验,极大地保障了主干分支的健康度。

相关推荐
Yolanda_20222 小时前
vue-sync修饰符解析以及切换iframe页面进行保存提示功能的思路
前端·javascript·vue.js
Pu_Nine_92 小时前
深入理解节流(Throttle):原理、实现与应用场景
javascript·性能优化·es6·节流·lodash 库
伍哥的传说2 小时前
Vite Plugin PWA – 零配置构建现代渐进式Web应用
开发语言·前端·javascript·web app·pwa·service worker·workbox
ai产品老杨2 小时前
解锁仓储智能调度、运输路径优化、数据实时追踪,全功能降本提效的智慧物流开源了
javascript·人工智能·开源·音视频·能源
GDAL2 小时前
Quat.js四元数完全指南
javascript·quaternion
alphageek82 小时前
Electron开源库入门教程:跨平台桌面应用框架
javascript·其他·electron·开源
小桥风满袖3 小时前
极简三分钟ES6 - ES8中字符串扩展
前端·javascript
张拭心3 小时前
这就是流量的力量吗?用豆包 AI 编程做的xhs小组件帖子爆了
前端·ai编程·豆包marscode