从零构建一个现代化前端项目:我的实战经验和心路历程

技术乌托邦
2025-06-12 12:03
阅读 963

引言:为什么是“从零”?

大家好,我是阿杰,目前是一名工作五年多的前端工程师。在这些年里,我参与过不少大中型项目的开发,也带过几个从头搭建的新项目。要说最让我印象深刻的,还是一年前接手的一个新客户项目——那是一个全新的后台管理系统,完全从零开始,没有任何历史包袱。

当时客户给的时间紧、需求模糊,但要求必须上生产,还要支持IE11(你没听错,某些行业还在用这个),而且希望技术栈是“现代”的,比如 Vue3 + TypeScript + Vite。听起来很理想化?别急,这中间踩过的坑、掉过的陷阱、做出的妥协和优化的细节,远比想象的复杂得多。

这篇分享就是基于那次经历写下的,希望能帮你少走点弯路,也能让我自己再复盘一遍,看看当年的决定是否经得起时间的考验。


一、项目背景与挑战:从一张白纸开始

我们接到的这个项目,是一个面向企业内部员工的后台系统,主要包括以下几个核心模块:

  • 用户管理
  • 订单审批
  • 数据分析看板
  • 消息通知中心

客户的需求文档很简略,但有几点特别明确:

  1. 必须兼容 IE11 🧨(虽然现在已不推荐)
  2. 使用 Vue3 + TypeScript
  3. 要求开发速度快、代码可维护性强
  4. 需要统一 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.vueUserCard.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

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝