从零构建一个现代化前端项目:我的实战经验和心路历程
引言:为什么是“从零”?
大家好,我是阿杰,目前是一名工作五年多的前端工程师。在这些年里,我参与过不少大中型项目的开发,也带过几个从头搭建的新项目。要说最让我印象深刻的,还是一年前接手的一个新客户项目——那是一个全新的后台管理系统,完全从零开始,没有任何历史包袱。
当时客户给的时间紧、需求模糊,但要求必须上生产,还要支持IE11(你没听错,某些行业还在用这个),而且希望技术栈是“现代”的,比如 Vue3 + TypeScript + Vite。听起来很理想化?别急,这中间踩过的坑、掉过的陷阱、做出的妥协和优化的细节,远比想象的复杂得多。
这篇分享就是基于那次经历写下的,希望能帮你少走点弯路,也能让我自己再复盘一遍,看看当年的决定是否经得起时间的考验。
一、项目背景与挑战:从一张白纸开始
我们接到的这个项目,是一个面向企业内部员工的后台系统,主要包括以下几个核心模块:
- 用户管理
- 订单审批
- 数据分析看板
- 消息通知中心
客户的需求文档很简略,但有几点特别明确:
- 必须兼容 IE11 🧨(虽然现在已不推荐)
- 使用 Vue3 + TypeScript
- 要求开发速度快、代码可维护性强
- 需要统一 UI 规范(最好有一套组件库)
初期遇到的问题:
- 团队对 Vue3 + TypeScript 的配合使用经验不足(尤其是动态类型的类型定义容易出错)
- Vue3 默认不支持 IE11,需要 polyfill 和配置处理
- 缺乏统一 UI 设计规范,页面风格难以统一
- 模块划分模糊,团队协作效率低
- 性能瓶颈在开发环境中已经显现
这些问题听起来可能不算大,但在实际执行时,往往会导致进度严重延迟。尤其是在客户临时变更需求的情况下,更是雪上加霜。
二、解决方案:选型 + 架构设计
为了解决上述问题,我们采取了以下策略:
1. 技术选型
| 技术栈 | 选择理由 |
|---|---|
| Vue3 + Composition API | 更好的逻辑复用能力,符合现代化趋势 |
| TypeScript | 提升代码健壮性、减少运行时错误 |
| Vite + Vue-Loader | 开发体验极佳,热更新速度飞快 |
| Element Plus | 官方提供 IE11 兼容方案,适合企业级 UI 组件 |
| Pinia | 替代 Vuex,更小更易用的状态管理工具 |
| ESLint + Prettier | 统一团队编码风格 |
当然,如果你不需要 IE11 支持,可以去掉
core-js和部分 Babel 插件,性能会更好。
2. 工程结构设计(分层 + 模块化)
我们将整个项目拆分为以下几个层级:
src/
│
├── assets/ # 静态资源
├── components/ # 可复用业务组件
├── layout/ # 页面布局组件(如顶部导航、侧边栏等)
├── pages/ # 各功能页面
├── router/ # Vue Router 配置
├── stores/ # Pinia 状态管理模块
├── services/ # 接口调用封装
├── hooks/ # 自定义组合式函数(useXXX)
├── utils/ # 通用工具函数
├── plugins/ # 插件初始化逻辑
├── locales/ # 多语言支持文件
└── main.ts # 入口文件
通过这种结构,我们实现了职责清晰、方便协作的目标,也为后期维护打下了基础。
三、关键实践:如何解决主要问题
1. 如何让 Vue3 支持 IE11?
Vue3 官方默认不支持 IE11,所以需要手动添加一些 polyfill。
我们在项目入口处引入了 core-js 来补齐缺失特性:
// main.ts
import 'core-js/stable'
import 'regenerator-runtime/runtime'
同时,在 vite.config.ts 中增加 Babel 插件,转译 ES6+ 语法:
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import legacy from '@vitejs/plugin-legacy'
export default defineConfig({
plugins: [
vue(),
legacy({
targets: { ie: '11' },
additionalLegacyPolyfills: ['regenerator-runtime/runtime']
})
],
build: {
target: 'es2015',
polyfillModulePreload: true,
}
})
注意:这样打包出来的体积会比较大,建议在上线前进行压缩处理,或者给客户说明放弃支持 IE 的好处。
2. 如何提升多人协作效率?
- 使用 Git Flow 分支管理流程
- 用 Husky + Lint-staged 做提交前代码检查
- 统一组件命名规范,如:
BaseButton.vue、UserCard.vue - 引入 Storybook 做组件开发环境,方便演示和测试
- 统一接口请求方式(统一 service 层封装)
例如我们在 services/index.ts 中做如下封装:
import axios from 'axios'
const service = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 10000
})
service.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) config.headers.Authorization = `Bearer ${token}`
return config
})
export default service
每个业务模块都通过引入 service 发起请求,保证统一性和可维护性。
3. 性能优化:不只是上线才考虑
我们在开发初期就加入了性能监控工具。
- 使用 Lighthouse 测试加载性能
- 图片懒加载 + WebP 格式转换
- 合理的路由懒加载:
const AnalysisPage = () => import('../pages/AnalysisPage.vue')
- 减少全局引入 Element Plus 组件(改为按需加载 + unplugin-vue-components)
安装插件:
npm install -D unplugin-vue-components
配置 vite.config.ts:
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default defineConfig({
plugins: [
// ...其他插件
Components({
resolvers: [ElementPlusResolver()]
})
]
})
这样就不用手动导入每一个 Element Plus 组件,还能实现按需加载,节省包体积。
四、代码示例:几个典型场景
1. 实现用户登录状态持久化(Pinia 示例)
// stores/userStore.ts
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useUserStore = defineStore('user', () => {
const userInfo = ref(null)
function login(data) {
userInfo.value = data
localStorage.setItem('userInfo', JSON.stringify(data))
}
function logout() {
userInfo.value = null
localStorage.removeItem('userInfo')
}
function init() {
const stored = localStorage.getItem('userInfo')
if (stored) {
userInfo.value = JSON.parse(stored)
}
}
return { userInfo, login, logout, init }
})
// main.ts 初始化 store
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')
// 初始化用户状态
const userStore = useUserStore()
userStore.init()
2. 封装一个可复用的数据表格组件(Table 组件简化版)
<template>
<el-table :data="tableData" border style="width: 100%">
<el-table-column v-for="col in columns" :key="col.prop" :label="col.label">
<template #default="{ row }">
{{ row[col.prop] }}
</template>
</el-table-column>
</el-table>
</template>
<script setup>
defineProps({
tableData: {
type: Array,
required: true
},
columns: {
type: Array,
required: true
}
})
</script>
使用方式:
<template>
<base-table :table-data="orders" :columns="columns" />
</template>
<script setup>
import BaseTable from '@/components/BaseTable.vue'
const orders = ref([
{ id: 1, name: '订单A', amount: 99.9 },
{ id: 2, name: '订单B', amount: 199.9 }
])
const columns = [
{ prop: 'id', label: 'ID' },
{ prop: 'name', label: '名称' },
{ prop: 'amount', label: '金额' }
]
</script>
五、踩坑经验分享
1. TypeScript 类型推导不准确导致的 bug
早期我们在 service 中封装了一个通用响应结构:
interface BaseResponse<T> {
code: number
message: string
data: T
}
但有个接口返回字段不一致,结果编译器没报错,却运行时报错访问了不存在的字段。后来改用 Zod 做数据校验:
import { z } from 'zod'
const OrderSchema = z.object({
orderId: z.number(),
status: z.string(),
})
type Order = z.infer<typeof OrderSchema>
这样可以在服务端返回后立即校验数据结构是否匹配,避免运行时错误。
2. Vue Devtools 不显示组件名称问题
由于用了自动注册组件的方式,DevTools 显示的组件都是 <Unknown>。解决方法是在组件文件顶部加上:
defineOptions({ name: 'UserCard' })
3. IE11 下箭头函数报错:Unexpected token =>
这个问题源于我们误用了动态导入 (import()),在 IE11 上无法识别。解决方法是将动态导入改为静态导入,或在打包配置中关闭 tree-shaking。
六、效果总结:上线后的变化
经过两个月的紧张开发,项目如期交付。最终效果如下:
- 开发效率提升了约 30%,得益于 Vite + TypeScript 的强提示和模块化架构
- 包体积控制在合理范围内(主 JS 文件约 1MB,压缩后 300KB 左右)
- 用户反馈良好,交互流畅,尤其感谢懒加载带来的首屏提速
- 项目上线半年内无重大线上事故,稳定性达标
最重要的是,团队在这个过程中积累了宝贵的技术沉淀,后续类似的项目可以直接复用模板工程,快速上手。
七、我的建议与注意事项
结合这次项目经历,我想给正在或准备从零构建现代化前端项目的你几条建议:
✅ 技术选型不要盲目追新,要结合业务需求
比如如果你需要兼容旧浏览器,Vite 是个不错的选择,但一定要注意 polyfill 和构建配置,否则可能出现白屏或者报错。
✅ 结构先行,架构先行
不要一开始就把所有逻辑塞进 App.vue。合理的模块划分和目录结构是你未来能否维护项目的关键。
✅ 写代码前先写 Type,TS 是你的最佳搭档
类型系统会让你提前发现问题,而不是等到 runtime 才知道哪里错了。
✅ 性能不是上线才考虑的事
越早引入性能监测手段越好,Lighthouse、Chrome Performance 面板、Vue Devtools 都是非常实用的调试工具。
✅ 文档和沟通一样重要
特别是在敏捷开发中,良好的注释和文档记录可以帮助新人更快融入团队,也能在回顾时帮助你梳理思路。
最后的一点感悟
写完这篇文章,我回想起那段加班赶进度的日子,虽然辛苦,但真的学到了很多。项目初期总想着“先把功能跑起来”,但实际上每一步的架构决策都在影响着后期开发的速度和质量。
技术不是孤立的,它永远服务于业务,而架构才是连接两者的桥梁。
也许有一天,我们会彻底放弃 IE11 的兼容;也许某一天 Vue3 会被新的框架取代。但那些关于项目结构、工程规范、协作方式的经验,却是永恒适用的。
希望这篇来自真实项目的分享,能在你即将启动下一个项目之前,带来一点点启发。
如果你有任何问题,欢迎留言交流,我也愿意继续分享更多的实战案例。
一起加油吧!🚀

评论 0