裸辞半年后,我用 Vue3 重构了老项目

CPU烧开水
2025-12-28 15:48
阅读 614

去年十一月,我从某大厂“战略性撤退”——说白了就是裸辞了。Gap 半年里,一边在上海租房续命(房租贵得我怀疑人生),一边折腾各种新技术:Svelte、Qwik、甚至玩了一阵 WebAssembly。但现实很骨感:面试官一问“你最近在做什么项目?”,我支支吾吾只能拿 GitHub 上几个玩具 demo 出来撑场面。

于是上个月重新找工作前,我咬牙决定:用 Vue.js 生态从零到一搞个像样的实战项目。不为别的,就为了简历上能写一句“独立完成全栈 SPA 应用开发”。结果这一折腾,踩的坑比我过去两年在大厂踩的都多。

老项目的债,终究要还

事情起因是帮前同事维护一个内部管理系统。那是个典型的 Vue2 + Options API 老古董,代码耦合得像泡面——面条缠在一起,想捞一根出来都难。最离谱的是,某个列表页加载 500 条数据直接卡死,产品经理上周五晚上十点发消息:“能不能优化下?明天演示给老板看。”

我打开 DevTools 一看,好家伙,v-for 里嵌套三层 v-if,每个组件都在 mounted 里发三个请求……当时真的想砸电脑。但冷静下来一想:这不正是练手 Vue3 Composition API 的绝佳机会吗?

从 create-vue 到 Pinia:现代 Vue 开发流水线

这次我决定彻底现代化。先跑官方脚手架:

npm create vue@3

一路回车选了 TypeScript + Pinia + Vue Router + Vitest。说实话,Vite 的热更新速度让我感动哭了——以前在公司 Webpack 构建动不动 30 秒,现在改一行代码瞬间刷新,幸福感拉满。

状态管理:告别 Vuex 的仪式感

以前在大厂写 Vuex,光是 mapStatemapActions 就能写半屏。现在 Pinia 直接导出 store 实例,组合式函数里直接解构使用:

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

export const useUserStore = defineStore('user', () => {
  const userInfo = ref<User | null>(null)
  
  const fetchUser = async () => {
    const res = await api.getUser()
    userInfo.value = res.data
  }

  return { userInfo, fetchUser }
})

在组件里:

<script setup>
import { useUserStore } from '@/stores/user'
const { userInfo, fetchUser } = useUserStore()
</script>

没有 modules 嵌套地狱,没有命名空间冲突,连 TypeScript 类型推导都丝滑。难怪公司新项目都偷偷切 Pinia 了——虽然 PM 还在用“状态管理框架”这种过时术语。

组件通信:Provide/Inject 的优雅时刻

有个需求:全局主题切换。以前的做法是在根组件放个 theme 状态,层层 props 透传下去。现在用 Provide/Inject 配合 Composables:

// composables/useTheme.ts
import { provide, inject, ref } from 'vue'

const ThemeSymbol = Symbol()

export function provideTheme() {
  const theme = ref<'light' | 'dark'>('light')
  provide(ThemeSymbol, theme)
  return { theme }
}

export function useTheme() {
  const theme = inject(ThemeSymbol)
  if (!theme) throw new Error('useTheme() must be called after provideTheme()')
  return theme
}

App.vue 里调用 provideTheme(),任何子孙组件直接 const theme = useTheme() 拿到响应式引用。再也不用担心中间组件“吃掉”props 了!

性能优化:从卡成 PPT 到丝般顺滑

回到最初那个 500 条数据的列表页。重构后做了三件事:

  1. 虚拟滚动:用 vue-virtual-scroller 替代原生 v-for
  2. 懒加载图片<img v-lazy="item.avatar">
  3. 计算属性缓存:把格式化时间等操作移到 computed

关键代码:

<template>
  <RecycleScroller
    class="scroller"
    :items="filteredItems"
    :item-size="60"
    key-field="id"
  >
    <template #default="{ item }">
      <UserItem :user="item" />
    </template>
  </RecycleScroller>
</template>

<script setup>
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'

const items = ref([]) // 500+ 条数据
const searchKeyword = ref('')

// 计算属性自动缓存!
const filteredItems = computed(() => 
  items.value.filter(item => 
    item.name.includes(searchKeyword.value)
  )
)
</script>

效果立竿见影:内存占用从 400MB 降到 80MB,滚动帧率稳定 60fps。测试同学惊呼:“这还是同一个页面?”

调试技巧:Vue DevTools 6 的隐藏彩蛋

这次开发中发现 Vue DevTools 6 有个神功能:组件依赖图谱。点击任意组件,能看到它依赖哪些 props、响应式变量,以及被哪些组件引用。

有次遇到一个诡异 bug:搜索框输入后列表没更新。通过依赖图谱发现,filteredItems 计算属性居然没追踪到 searchKeyword —— 原来我把 searchKeyword 定义成了普通变量而不是 ref!这种问题以前只能 console.log 到天荒地老。

GitHub 上那些救我命的资源

作为技术博客重度用户,我整理了一份 Vue3 生态必备资源清单(已同步到 GitHub):

类别 推荐资源 为什么香
UI 库 Naive UI TypeScript 完美支持,暗黑模式开箱即用
工具库 VueUse 70+ 个 Composition 函数,省下 80% 轮子代码
脚手架 Vitesse Anthony Fu 大神的 starter,集成最佳实践
学习 Vue Mastery 免费课 手把手教 Composition API

特别安利 VueUse 的 useDebounceFn——解决搜索防抖只需一行:

const debouncedSearch = useDebounceFn(() => {
  // 执行搜索
}, 300)

再也不用手写 debounce 工具函数了!

代码人生的顿悟时刻

重构完这个项目,我突然理解了 Vue 团队的设计哲学:用最小的认知成本解决最大范围的问题。Composition API 不是炫技,而是让逻辑复用变得像写函数一样自然;Pinia 不是替代 Vuex,而是消除模板代码的仪式感。

上周面试时,面试官问我:“为什么选择 Vue 而不是 React?” 我笑着回答:“因为当我深夜加班改 Bug 时,Vue 的报错信息会告诉我第几行第几个字符错了——而 React 只会说 ‘Something went wrong’。”

当然,这只是玩笑。真正的原因是:Vue 让我更专注于业务逻辑,而不是框架本身。在大厂时我们总追求“高大上”的技术栈,却忘了前端的核心使命——交付用户体验。这次裸辞后的实战让我重新找回了 coding 的初心。

如果你也在维护祖传 Vue2 项目,或者正准备入坑 Vue3,不妨试试这些方案。我的 GitHub 仓库 已开源完整代码,欢迎 star & issue(求轻喷,毕竟 Gap 期间写的代码可能有点野)。

最后送大家一句我在工位贴了三年的话:“代码即人生,删掉冗余,留下优雅。”

P.S. 租房合同快到期了,如果这篇博客帮你拿到了 offer,记得请我喝杯瑞幸(上海静安寺附近) 😏

评论 0

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