在2026年的今天,Vue 3 + TypeScript 已成为企业级前端开发的黄金组合。根据最新技术调研,超过 75% 的中大型前端项目已采用这一技术栈。然而,很多开发者在使用时仍停留在"能跑就行"的阶段,缺乏系统性的架构设计思维。
本文将从工程规范 、类型安全 、架构设计 、性能优化四个维度,分享一套经过多个真实项目验证的最佳实践方案。
一、项目初始化与工程配置
1.1 使用 Vite 创建项目
# 推荐使用官方脚手架
npm create vue@latest my-enterprise-app
# 项目配置选项
✔ Project name: ... my-enterprise-app
✔ Add TypeScript? ... Yes
✔ Add JSX Support? ... No
✔ Add Vue Router? ... Yes
✔ Add Pinia? ... Yes
✔ Add Vitest? ... Yes
✔ Add ESLint? ... Yes
✔ Add Prettier? ... Yes
1.2 核心依赖安装
# UI组件库(按需选择)
npm install element-plus @element-plus/icons-vue
# HTTP请求
npm install axios
# 工具库
npm install lodash-es dayjs
# 类型定义
npm install -D @types/lodash-es
1.3 TypeScript 配置优化
// tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve",
"strict": true, // 开启严格模式
"noUnusedLocals": true, // 检查未使用变量
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"paths": { // 路径别名
"@/*": ["./src/*"],
"@components/*": ["./src/components/*"],
"@views/*": ["./src/views/*"],
"@api/*": ["./src/api/*"],
"@stores/*": ["./src/stores/*"],
"@utils/*": ["./src/utils/*"],
"@types/*": ["./src/types/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"exclude": ["node_modules", "dist"]
}
二、企业级项目目录结构
src/
├── api/ # API接口层
│ ├── modules/ # 按业务模块划分
│ │ ├── user.ts
│ │ ├── order.ts
│ │ └── product.ts
│ ├── index.ts # 统一导出
│ └── types.ts # API类型定义
├── assets/ # 静态资源
│ ├── images/
│ ├── styles/
│ │ ├── variables.scss # 全局变量
│ │ ├── mixins.scss # SCSS混入
│ │ └── global.scss # 全局样式
│ └── icons/ # SVG图标
├── components/ # 公共组件
│ ├── base/ # 基础组件
│ │ ├── BaseButton.vue
│ │ ├── BaseTable.vue
│ │ └── BaseModal.vue
│ ├── business/ # 业务组件
│ └── layout/ # 布局组件
├── composables/ # 组合式函数
│ ├── useLoading.ts
│ ├── usePagination.ts
│ └── usePermission.ts
├── config/ # 配置文件
│ ├── env.ts # 环境变量
│ └── constants.ts # 常量定义
├── directives/ # 自定义指令
│ ├── permission.ts
│ └── loading.ts
├── hooks/ # 业务Hooks
├── layouts/ # 布局文件
│ ├── DefaultLayout.vue
│ └── AdminLayout.vue
├── router/ # 路由配置
│ ├── index.ts
│ ├── routes.ts # 路由表
│ └── guards.ts # 路由守卫
├── stores/ # Pinia状态管理
│ ├── modules/
│ │ ├── user.ts
│ │ └── app.ts
│ └── index.ts
├── types/ # TypeScript类型定义
│ ├── api.d.ts
│ ├── common.d.ts
│ └── vue-shim.d.ts
├── utils/ # 工具函数
│ ├── request.ts # Axios封装
│ ├── storage.ts # 本地存储
│ └── validate.ts # 验证工具
├── views/ # 页面组件
│ ├── login/
│ ├── dashboard/
│ └── system/
├── App.vue
└── main.ts
三、核心模块最佳实践
3.1 Axios 封装与类型安全
// src/utils/request.ts
import axios, {
type AxiosInstance,
type AxiosRequestConfig,
type AxiosResponse
} from 'axios'
import { useUserStore } from '@/stores/user'
import { ElMessage } from 'element-plus'
// 响应数据类型
export interface ResponseData<T = any> {
code: number
data: T
message: string
}
class Request {
private instance: AxiosInstance
constructor(baseConfig: AxiosRequestConfig) {
this.instance = axios.create(baseConfig)
this.setupInterceptors()
}
private setupInterceptors() {
// 请求拦截器
this.instance.interceptors.request.use(
(config) => {
const userStore = useUserStore()
if (userStore.token) {
config.headers.Authorization = `Bearer ${userStore.token}`
}
return config
},
(error) => Promise.reject(error)
)
// 响应拦截器
this.instance.interceptors.response.use(
(response: AxiosResponse<ResponseData>) => {
const { code, data, message } = response.data
if (code === 200) {
return data
}
ElMessage.error(message || '请求失败')
return Promise.reject(new Error(message))
},
(error) => {
ElMessage.error(error.message || '网络错误')
return Promise.reject(error)
}
)
}
public request<T>(config: AxiosRequestConfig): Promise<T> {
return this.instance.request(config)
}
public get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
return this.instance.get(url, config)
}
public post<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
return this.instance.post(url, data, config)
}
}
export const request = new Request({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 10000,
headers: {
'Content-Type': 'application/json'
}
})
3.2 API 模块化设计
// src/api/modules/user.ts
import { request } from '@/utils/request'
import type { UserInfo, LoginParams, LoginResult } from '@/types/api'
export const userApi = {
// 用户登录
login: (params: LoginParams) =>
request.post<LoginResult>('/auth/login', params),
// 获取用户信息
getUserInfo: () =>
request.get<UserInfo>('/user/info'),
// 更新用户信息
updateUserInfo: (data: Partial<UserInfo>) =>
request.put<UserInfo>('/user/info', data),
// 退出登录
logout: () =>
request.post('/auth/logout')
}
3.3 Pinia 状态管理
// src/stores/modules/user.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { userApi } from '@/api/modules/user'
import type { UserInfo } from '@/types/api'
export const useUserStore = defineStore('user', () => {
// State
const token = ref<string>(localStorage.getItem('token') || '')
const userInfo = ref<UserInfo | null>(null)
// Getters
const isLoggedIn = computed(() => !!token.value)
const userName = computed(() => userInfo.value?.name || '访客')
const userRoles = computed(() => userInfo.value?.roles || [])
// Actions
async function login(params: { username: string; password: string }) {
const result = await userApi.login(params)
token.value = result.token
localStorage.setItem('token', result.token)
await fetchUserInfo()
}
async function fetchUserInfo() {
userInfo.value = await userApi.getUserInfo()
}
async function logout() {
await userApi.logout()
token.value = ''
userInfo.value = null
localStorage.removeItem('token')
}
return {
token,
userInfo,
isLoggedIn,
userName,
userRoles,
login,
fetchUserInfo,
logout
}
})
3.4 组合式函数(Composables)
// src/composables/usePagination.ts
import { ref, reactive } from 'vue'
export interface PaginationConfig {
page: number
pageSize: number
total: number
}
export function usePagination(initialConfig: Partial<PaginationConfig> = {}) {
const pagination = reactive<PaginationConfig>({
page: 1,
pageSize: 10,
total: 0,
...initialConfig
})
const currentPage = computed({
get: () => pagination.page,
set: (val) => { pagination.page = val }
})
function reset() {
pagination.page = 1
pagination.total = 0
}
function setTotal(total: number) {
pagination.total = total
}
return {
pagination,
currentPage,
reset,
setTotal
}
}
// src/composables/useLoading.ts
import { ref } from 'vue'
export function useLoading(initialValue = false) {
const loading = ref(initialValue)
const startLoading = () => { loading.value = true }
const stopLoading = () => { loading.value = false }
const withLoading = async <T>(fn: () => Promise<T>): Promise<T> => {
startLoading()
try {
return await fn()
} finally {
stopLoading()
}
}
return {
loading,
startLoading,
stopLoading,
withLoading
}
}
四、组件开发规范
4.1 使用 <script setup> 语法糖
<!-- src/components/base/BaseTable.vue -->
<script setup lang="ts">
import { ref, computed, defineProps, defineEmits } from 'vue'
import type { PropType } from 'vue'
// 类型定义
export interface TableColumn {
prop: string
label: string
width?: number
sortable?: boolean
}
export interface TableData {
[key: string]: any
}
// Props 定义(推荐方式)
const props = defineProps<{
columns: TableColumn[]
data: TableData[]
loading?: boolean
pagination?: {
page: number
pageSize: number
total: number
}
}>()
// Emits 定义
const emit = defineEmits<{
(e: 'page-change', page: number): void
(e: 'size-change', size: number): void
(e: 'sort-change', { prop, order }: { prop: string; order: string }): void
}>()
// 计算属性
const tableHeight = computed(() => {
return props.data.length > 10 ? '500px' : 'auto'
})
// 事件处理
const handlePageChange = (page: number) => {
emit('page-change', page)
}
</script>
<template>
<el-table
:data="data"
:loading="loading"
:height="tableHeight"
v-bind="$attrs"
>
<el-table-column
v-for="col in columns"
:key="col.prop"
:prop="col.prop"
:label="col.label"
:width="col.width"
:sortable="col.sortable"
/>
</el-table>
<el-pagination
v-if="pagination"
:current-page="pagination.page"
:page-size="pagination.pageSize"
:total="pagination.total"
@current-change="handlePageChange"
/>
</template>
4.2 组件类型导出
// src/components/base/index.ts
export { default as BaseButton } from './BaseButton.vue'
export { default as BaseTable } from './BaseTable.vue'
export { default as BaseModal } from './BaseModal.vue'
// 导出类型
export type { TableColumn, TableData } from './BaseTable.vue'
五、路由架构设计
// src/router/routes.ts
import type { RouteRecordRaw } from 'vue-router'
export const constantRoutes: RouteRecordRaw[] = [
{
path: '/login',
name: 'Login',
component: () => import('@/views/login/index.vue'),
meta: { title: '登录', hidden: true }
},
{
path: '/404',
name: '404',
component: () => import('@/views/error/404.vue'),
meta: { title: '404', hidden: true }
}
]
export const asyncRoutes: RouteRecordRaw[] = [
{
path: '/',
component: () => import('@/layouts/DefaultLayout.vue'),
children: [
{
path: '',
name: 'Dashboard',
component: () => import('@/views/dashboard/index.vue'),
meta: { title: '首页', icon: 'dashboard' }
},
{
path: 'system',
name: 'System',
meta: { title: '系统管理', icon: 'setting' },
children: [
{
path: 'user',
name: 'UserManage',
component: () => import('@/views/system/user/index.vue'),
meta: { title: '用户管理', permission: ['user:list'] }
}
]
}
]
}
]
// src/router/guards.ts
import type { Router } from 'vue-router'
import { useUserStore } from '@/stores/user'
export function setupRouterGuards(router: Router) {
router.beforeEach(async (to, from, next) => {
const userStore = useUserStore()
// 设置页面标题
document.title = to.meta.title
? `${to.meta.title} - 管理系统`
: '管理系统'
// 登录验证
if (to.path !== '/login' && !userStore.isLoggedIn) {
next(`/login?redirect=${to.path}`)
return
}
// 权限验证
if (to.meta.permission) {
const hasPermission = to.meta.permission.some((p: string) =>
userStore.userRoles.includes(p)
)
if (!hasPermission) {
next('/403')
return
}
}
next()
})
}
六、性能优化策略
6.1 组件懒加载
// 路由懒加载(默认支持)
const UserManage = () => import('@/views/system/user/index.vue')
// 组件异步加载
const HeavyComponent = defineAsyncComponent(() =>
import('@/components/business/HeavyComponent.vue')
)
6.2 列表虚拟滚动
<template>
<el-table-v2
:columns="columns"
:data="largeData"
:width="800"
:height="600"
/>
</template>
6.3 计算属性缓存
// 利用 computed 的缓存特性
const filteredList = computed(() => {
return props.list.filter(item => item.status === 'active')
})
七、代码规范与质量保障
7.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',
'prettier'
],
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@typescript-eslint/parser',
ecmaVersion: 'latest',
sourceType: 'module'
},
rules: {
'vue/multi-word-component-names': 'off',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/explicit-function-return-type': 'warn'
}
}
7.2 Git Hooks 配置
// package.json
{
"scripts": {
"prepare": "husky install",
"lint": "eslint . --ext .vue,.js,.ts,.jsx,.tsx --fix",
"format": "prettier --write ."
},
"devDependencies": {
"husky": "^8.0.0",
"lint-staged": "^15.0.0"
},
"lint-staged": {
"*.{vue,js,ts,jsx,tsx}": [
"eslint --fix",
"prettier --write"
]
}
}
八、总结与建议
📋 核心要点回顾
| 维度 | 最佳实践 |
|---|---|
| 构建工具 | Vite 5.x(极速热更新) |
| 状态管理 | Pinia(完整TS支持) |
| 代码风格 | <script setup> + Composition API |
| 类型安全 | 严格模式 + 完整类型定义 |
| 代码规范 | ESLint + Prettier + Husky |
| API设计 | 模块化 + 统一响应类型 |
🚀 进阶建议
- 渐进式迁移:老项目可逐步引入 TypeScript,从工具函数开始
- 类型优先:先定义接口类型,再实现业务逻辑
- 组件文档:使用 Storybook 或 VitePress 建立组件文档
- 单元测试:核心业务逻辑编写 Vitest 测试用例
- 性能监控:接入前端监控平台(如 Sentry)