Vue.js生态实战:从项目重构到GitHub开源的一路踩坑

卓越发明家
2025-12-28 15:14
阅读 724

上周五晚上九点半,我在国贸地铁站拖着疲惫的身体往家走——又是一天和Vue组件死磕的日子。作为一个天天调参炼丹的AI算法工程师,按理说前端不该是我的主战场。但自从去年我们团队接了个内部可视化平台项目,领导一句“你不是懂点前端嘛”,我就被迫重拾Vue,结果一发不可收拾。

这半年下来,我不仅把Vue3 + TypeScript + Vite这套组合拳打得滚瓜烂熟,还顺手在GitHub上开源了一个轻量级UI组件库。今天这篇技术分享,就是想把这一路踩过的坑、优化过的性能、调教过的构建流程,原原本本地记录下来。希望对正在Vue生态里摸爬滚打的兄弟们有点帮助。

起因:一个被产品经理“逼”出来的重构

事情要从去年双11说起。当时我们组要给运营团队做一个实时数据看板,展示大促期间的模型效果指标。初版是用Vue2 + Webpack搭的,赶工上线后勉强能用,但体验相当拉胯:

  • 首屏加载超过5秒(运营小姐姐当场翻白眼)
  • 切换Tab时卡顿明显(“你们这页面是不是卡了?”)
  • 构建时间长达3分钟(每次改个颜色都要喝杯咖啡等)

更糟的是,随着需求不断叠加,代码逐渐变成意大利面条——父子组件通信靠$emit传三代,状态管理全靠props层层透传,Vuex store里塞满了各种临时变量。

终于有一天,产品经理拍着桌子说:“能不能像Ant Design Pro那样丝滑?人家那个Table切换数据都不带卡的!”

行吧,重构就重构。反正我也受够了Webpack那慢如蜗牛的HMR(Hot Module Replacement)。

技术选型:为什么是Vue3 + Vite?

说实话,一开始我也犹豫过要不要直接上React。毕竟我们团队日常写PyTorch,JSX那一套逻辑更贴近函数式思维。但考虑到现有代码都是Vue,且团队里有老哥对Vue有执念,最终还是决定拥抱Vue3。

核心选型理由如下:

  • Composition API:逻辑复用比Options API清晰太多,尤其适合我们这种复杂交互场景
  • Vite的闪电构建:开发服务器启动<500ms,HMR更新基本无感
  • TypeScript原生支持:再也不用担心props类型对不上导致运行时报错
  • Tree-shaking更彻底:打包体积比Vue2小30%左右(实测数据)

于是,一个周末,我新建了仓库,命名为 vue-dashboard-pro,并默默推到了GitHub。没想到后来居然收获了200+ star,也算意外之喜。

实战一:状态管理的优雅解法

旧项目里,Vuex的store长得像一锅乱炖:

// 旧Vuex store(不忍直视)
state: {
  selectedDate: null,
  tableData: [],
  isLoading: false,
  errorMsg: '',
  chartConfig: {},
  userPreferences: {}, // 甚至把用户偏好也塞进来了...
}

重构时我做了两件事:

  1. 拆分模块:按功能域划分store(dashboard.ts, table.ts, chart.ts
  2. 拥抱Pinia:Vue官方推荐的新一代状态管理库,TypeScript支持极佳

Pinia的写法简直清爽:

// stores/table.ts
import { defineStore } from 'pinia'

export const useTableStore = defineStore('table', () => {
  const data = ref<TableRow[]>([])
  const loading = ref(false)
  const pagination = reactive({ page: 1, size: 20 })

  const fetchData = async () => {
    loading.value = true
    try {
      data.value = await api.getTableData(pagination)
    } finally {
      loading.value = false
    }
  }

  return { data, loading, pagination, fetchData }
})

在组件中使用:

<script setup lang="ts">
import { useTableStore } from '@/stores/table'

const tableStore = useTableStore()
onMounted(() => tableStore.fetchData())
</script>

效果:代码可读性提升80%,单元测试也更容易写(直接mock store即可)。更重要的是,再也不用在.vue文件顶部写一长串mapState, mapActions了。

实战二:性能优化——让60fps成为常态

最开始重构完,自测感觉流畅多了。但一拿到真实数据(10万行表格),Chrome DevTools的Performance面板直接给我上了一课:FPS掉到15!

问题定位

通过录制用户操作,发现瓶颈主要在:

  • 虚拟滚动缺失:一次性渲染全部DOM节点
  • 响应式数据过度监听:每个表格行都变成响应式对象
  • 重复计算:filter/sort逻辑没做缓存

解决方案

1. 引入虚拟滚动

我对比了vue-virtual-scrollervueuc两个库,最终选了后者——更轻量(gzip后仅4KB),且支持动态高度。

<template>
  <VirtualList 
    :items="visibleData" 
    :item-height="56"
    class="table-body"
  >
    <template #default="{ item }">
      <TableRow :data="item" />
    </template>
  </VirtualList>
</template>

注:visibleData是经过filter/sort后的数据,但只渲染可视区域内的行(通常20-30条)

2. 使用shallowRef避免深度响应式

对于纯展示型的大数据,不需要深度监听:

// 将原始数据转为非响应式
const rawData = shallowRef<TableRow[]>([])

// 在计算属性中处理
const visibleData = computed(() => {
  return rawData.value
    .filter(row => row.status === filterStatus)
    .slice(0, 1000) // 先限制数量
})

3. 计算属性缓存

利用computed的缓存特性,避免重复执行昂贵操作:

const sortedData = computed(() => {
  console.log('sorting...') // 这行只会打印一次(只要依赖不变)
  return [...visibleData.value].sort((a, b) => a.id - b.id)
})

优化前后对比

指标 优化前 优化后 提升
首屏加载 5.2s 1.8s 65% ↓
表格滚动FPS 15 58 287% ↑
Bundle Size 2.1MB 1.4MB 33% ↓

现在运营小姐姐终于不翻白眼了,甚至主动问:“这个页面能不能做成我们所有系统的模板?”

实战三:构建部署与GitHub Actions自动化

作为算法工程师,我对CI/CD本来没啥概念。但自从某次上线把生产环境搞挂了(别问,问就是npm run build忘加--mode production),我就下定决心搞一套自动化流程。

Vite配置优化

首先调整vite.config.ts

export default defineConfig({
  build: {
    target: 'es2015',
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true, // 自动移除console.log
        drop_debugger: true
      }
    },
    rollupOptions: {
      output: {
        manualChunks: {
          // 分离第三方库
          vendor: ['vue', 'pinia', 'axios'],
          chart: ['echarts']
        }
      }
    }
  }
})

GitHub Actions流水线

.github/workflows/deploy.yml中配置:

name: Deploy to Production

on:
  push:
    branches: [main]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Setup Node
        uses: actions/setup-node@v3
        with:
          node-version: 18
          
      - name: Install deps
        run: npm ci
        
      - name: Build
        run: npm run build
        
      - name: Deploy to OSS
        uses: manyuanrong/setup-ossutil@master
        with:
          endpoint: ${{ secrets.OSS_ENDPOINT }}
          access-key-id: ${{ secrets.OSS_KEY_ID }}
          access-key-secret: ${{ secrets.OSS_KEY_SECRET }}
        run: ossutil cp -r dist/ oss://my-bucket/dashboard/

现在每次合并到main分支,代码会自动构建并上传到阿里云OSS,配合CDN,全球访问速度飞起。再也不用半夜被运维电话叫醒:“你上次上线的静态资源404了!”

开源分享:把轮子造得漂亮一点

做完内部项目后,我觉得有些通用组件(比如那个高性能表格、日期范围选择器)值得抽象出来。于是花了一周时间,剥离业务逻辑,写了完善的文档和Demo,发布了vue-dashboard-ui到GitHub。

没想到收获了不少issue和PR。有个老哥甚至贡献了暗色主题,让我感动得差点流泪(程序员的眼泪很贵的好吗)。

开源过程中有几个心得:

  • 文档要详细:哪怕多花半天写README,也能省下无数解释的时间
  • Demo要可运行:最好提供StackBlitz或CodeSandbox链接
  • TypeScript定义必须完善:前端开发者现在很看重这个
  • 遵循SemVer版本规范:别动不动就breaking change

现在这个库已经成了我们团队新项目的标配,连隔壁Java组都在用。看来“不要重复造轮子”的前提,是你造的轮子得足够圆。

最后:Vue生态的思考

折腾完这一通,我对Vue生态有了更深的理解:

  • Vite确实是未来:开发体验碾压Webpack,尤雨溪诚不我欺
  • 组合式API更适合复杂逻辑:尤其是需要跨组件复用的场景
  • 生态工具链日趋成熟:从状态管理(Pinia)到测试(Vitest),Vue3的配套已经非常完善

当然,Vue也不是银弹。比如在超大型应用中,React的生态可能更丰富;对于需要极致性能的场景,Svelte或许更合适。但就我们这种中后台系统而言,Vue3 + TypeScript + Vite的组合,真的香。


写这篇文章的时候,窗外北京的夜色正浓。回想这半年,从被产品经理追着问“页面怎么这么卡”,到如今项目稳定运行、GitHub收到感谢issue,虽然过程充满debug的痛苦,但那种“搞定它”的成就感,大概就是程序员继续搬砖的动力吧。

如果你也在Vue的路上踩坑,欢迎来我的GitHub repo提issue——说不定你的问题,正是我明天要解决的。毕竟,技术分享的意义,不就在于互相照亮前行的路吗?

(完)

P.S. 本文所有代码示例均已脱敏,真实项目中的报错信息比这惨烈十倍。比如那个著名的“Cannot read property 'map' of undefined”,我现在听到都会PTSD……

评论 0

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