Vue3 + TypeScript 项目架构设计:从 0 搭建企业级前端工程

张开发
2026/4/15 3:28:00 15 分钟阅读

分享文章

Vue3 + TypeScript 项目架构设计:从 0 搭建企业级前端工程
一、为什么要关注架构经历过多个项目的迭代后我深刻体会到好的架构能让需求开发效率提升 3 倍而糟糕的架构会让简单的需求变得异常复杂。本文将分享一套经过生产环境验证的 Vue3 TS 项目架构适合中大型团队协作。二、项目目录结构src/ ├── api/ # 接口管理 │ ├── modules/ # 按业务模块划分 │ ├── interceptors.ts # 请求/响应拦截器 │ └── request.ts # axios 封装 ├── components/ # 公共组件 │ ├── business/ # 业务组件 │ └── common/ # 通用组件Button、Modal等 ├── composables/ # 组合式函数复用逻辑 ├── directives/ # 自定义指令 ├── hooks/ # 与 Vue 无关的工具函数 ├── layouts/ # 布局组件 ├── router/ # 路由配置 │ ├── index.ts │ └── routes.ts # 路由表 ├── stores/ # Pinia 状态管理 │ ├── modules/ │ └── index.ts ├── styles/ # 全局样式 │ ├── variables.scss # SCSS 变量 │ └── mixins.scss # 混入 ├── types/ # 全局类型定义 ├── utils/ # 工具函数 ├── views/ # 页面视图 └── App.vue三、核心模块设计3.1 API 层封装类型安全的请求// api/request.ts import axios, { AxiosInstance, AxiosRequestConfig } from axios; interface ResponseDataT unknown { code: number; data: T; message: string; } class HttpClient { private instance: AxiosInstance; constructor(config: AxiosRequestConfig) { this.instance axios.create(config); this.setupInterceptors(); } private setupInterceptors() { // 请求拦截器 this.instance.interceptors.request.use( (config) { const token localStorage.getItem(token); if (token) config.headers.Authorization Bearer ${token}; return config; }, (error) Promise.reject(error) ); // 响应拦截器 this.instance.interceptors.response.use( (response) { const { data } response; if (data.code ! 200) { // 统一错误处理 handleError(data); return Promise.reject(data); } return data.data; }, (error) { handleNetworkError(error); return Promise.reject(error); } ); } getT(url: string, config?: AxiosRequestConfig): PromiseT { return this.instance.get(url, config); } postT(url: string, data?: unknown, config?: AxiosRequestConfig): PromiseT { return this.instance.post(url, data, config); } } export const http new HttpClient({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 10000 });3.2 类型安全的 API 定义// api/modules/user.ts import { http } from ../request; export interface UserInfo { id: number; username: string; avatar: string; email: string; } export interface LoginParams { username: string; password: string; } export const userApi { login: (params: LoginParams) http.poststring(/auth/login, params), getUserInfo: () http.getUserInfo(/user/info), updateProfile: (data: PartialUserInfo) http.postvoid(/user/update, data) };3.3 组合式函数逻辑复用// composables/useTable.ts import { ref, computed } from vue; interface UseTableOptionsT, Q { fetchFn: (params: Q) Promise{ list: T[]; total: number }; initialQuery?: Q; } export function useTableT, Q extends Recordstring, unknown(options: UseTableOptionsT, Q) { const { fetchFn, initialQuery {} as Q } options; const loading ref(false); const data refT[]([]); const total ref(0); const query refQ({ ...initialQuery, page: 1, pageSize: 10 }); const pagination computed(() ({ current: query.value.page as number, pageSize: query.value.pageSize as number, total: total.value })); const fetchData async () { loading.value true; try { const res await fetchFn(query.value); data.value res.list; total.value res.total; } finally { loading.value false; } }; const onPageChange (page: number) { query.value.page page; fetchData(); }; // 初始化加载 fetchData(); return { loading, data, pagination, query, fetchData, onPageChange }; } // 使用示例 const { loading, data, pagination, onPageChange } useTable({ fetchFn: userApi.getList, initialQuery: { keyword: } });3.4 Pinia Store 最佳实践// stores/modules/user.ts import { defineStore } from pinia; import { ref, computed } from vue; import { userApi, type UserInfo } from /api/modules/user; export const useUserStore defineStore(user, () { // State const userInfo refUserInfo | null(null); const token ref(localStorage.getItem(token) || ); // Getters const isLoggedIn computed(() !!token.value); const username computed(() userInfo.value?.username || ); // Actions const login async (loginForm: { username: string; password: string }) { const res await userApi.login(loginForm); token.value res; localStorage.setItem(token, res); await fetchUserInfo(); }; const fetchUserInfo async () { userInfo.value await userApi.getUserInfo(); }; const logout () { token.value ; userInfo.value null; localStorage.removeItem(token); }; return { userInfo, token, isLoggedIn, username, login, logout, fetchUserInfo }; });四、代码规范配置4.1 ESLint Prettier// .eslintrc.cjs module.exports { root: true, env: { browser: true, es2021: true, node: true }, extends: [ eslint:recommended, plugin:vue/vue3-recommended, plugin:typescript-eslint/recommended, plugin:prettier/recommended ], parser: vue-eslint-parser, parserOptions: { parser: typescript-eslint/parser, sourceType: module }, rules: { vue/multi-word-component-names: off, typescript-eslint/no-explicit-any: warn } };4.2 Git 提交规范使用huskylint-stagedcommitlint// commitlint.config.cjs module.exports { extends: [commitlint/config-conventional], rules: { type-enum: [2, always, [feat, fix, docs, style, refactor, test, chore]] } };提交格式feat: 添加用户登录功能 fix: 修复表格分页 BUG docs: 更新 README五、工程化工具链工具用途Vite构建工具比 Webpack 快 10 倍Vitest单元测试CypressE2E 测试Storybook组件文档Changesets版本管理六、总结一个好的前端架构应该具备类型安全- TypeScript 全覆盖逻辑复用- Composables 抽离公共逻辑模块清晰- 按功能分层职责单一规范约束- ESLint 提交规范性能优先- 按需加载、代码分割

更多文章