从零开始构建一个现代化前端项目:一个普通CS本科生的实战血泪史

AlgoMaster
2025-12-12 19:33
阅读 544

大家好,我是小张(真名就不说了,怕被前司同事认出来 😅),普通一本 CS 专业大四狗,去年秋招侥幸拿了个二线大厂 offer,现在正卡在“已拿到 offer、等入职”的尴尬期。说白了就是——人还在学校,心已经飘去工位了。

最近这几个月闲得发慌,又不想天天躺平刷 B 站,就想着折腾点东西练练手。正好之前在公司干了三年多前端,眼看着团队技术栈慢慢老化(React 16 + Webpack 4 的组合还在苟延残喘),自己也萌生了跳槽的念头。想跳槽就得有新东西能写进简历,于是咬咬牙,决定从零搭一个“现代化”的前端项目,目标是:能跑、好看、快、还能吹

顺便提一嘴,我习惯边听 Lo-fi Hip Hop 边写代码,BGM 一响,bug 就少一半(玄学)。今天这篇文,就是把整个过程扒开给你看——包括那些让我凌晨三点对着屏幕骂产品经理的瞬间。


为啥要重造轮子?因为产品需求太离谱!

事情的起因其实挺搞笑。上个月,我在 GitHub 上瞎逛,看到一个开源项目叫 daily-standup,是个极简站会打卡工具。界面干净,逻辑简单,但 UI 像 2012 年写的。我心想:“这玩意儿要是用现代技术重做一遍,说不定能当作品集项目。”

结果刚动手,噩梦就开始了。

我不是一个人在战斗——我虚构了一个“产品经理”角色(名字就叫 PM 老王吧),给他设定了几个“合理”需求:

  • 用户能匿名打卡今日状态(✅ / 🚫 / 🤔)
  • 实时看到团队其他成员的状态(要有 WebSocket!)
  • 支持深色模式
  • 手机端体验不能崩
  • 上线前必须通过 Lighthouse 评分 ≥90

最后一句直接给我整不会了。我们公司去年双11期间搞了个活动页,Lighthouse 性能分才 45,被测试组贴墙上嘲笑了半个月。这次我发誓不能再翻车。


技术选型:别卷了,求稳!

虽然最近在狂啃 Rust(Rust 真香,内存安全+零成本抽象,可惜前端用不上😭),但做 Web 项目还是得回归现实。我的原则是:用社区成熟方案,别炫技

最终敲定的技术栈如下:

类别 选型 理由
框架 React 18 + TypeScript 公司主力,熟;TS 能防我手滑
构建工具 Vite 4 启动快到飞起,Webpack 配置我都配吐了
状态管理 Zustand 轻量、无模板代码,Redux 我真的累了
UI 组件库 ShadCN/ui + Tailwind CSS 可定制性强,自带暗色模式支持
实时通信 Socket.IO (前端) + mock server 先本地模拟,后面可换真实后端
部署 Vercel 一键部署 + GitHub 集成,学生党福音

💡 吐槽时间:之前团队有个老哥非要上 Svelte,说“比 React 快 37%”。结果上线后发现 IE11 用户还有 2%,直接回滚。技术选型不是比谁新,而是比谁稳


初始化项目:Vite 真是救我狗命

以前用 Create React App,改个 .env 都要重启,Webpack 编译慢得像乌龟爬。这次直接上 Vite:

npm create vite@latest daily-standup -- --template react-ts
cd daily-standup
npm install
npm run dev

不到 3 秒,localhost:5173 打开了。我当场感动得想给尤雨溪磕一个。

接着装一堆依赖:

npm install zustand socket.io-client tailwindcss postcss autoprefixer
npx tailwindcss init -p

配置 Tailwind 也很简单,tailwind.config.js 里加个暗色模式支持:

// tailwind.config.js
module.exports = {
  content: ["./index.html", "./src/**/*.{js,jsx,ts,tsx}"],
  darkMode: "class", // 或 'media',但我喜欢手动切换
  theme: {
    extend: {},
  },
  plugins: [],
}

然后在 main.tsx 里挂上 Tailwind:

import React from 'react'
import ReactDOM from 'react-dom/client'
import './index.css' // 这里引入 @tailwind base/utilities/components
import App from './App'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)

搞定。这时候你可能会问:“CSS-in-JS 呢?”——别问,问就是 Tailwind 更适合快速原型开发。真要写复杂动画再上 Emotion 不迟。


状态管理:Zustand 让我告别 useEffect 地狱

以前用 Redux,光写 action + reducer 就能写半页纸。现在 Zustand 三行搞定全局状态:

// store/useStandupStore.ts
import { create } from 'zustand'

interface StandupState {
  status: 'done' | 'blocked' | 'thinking' | null
  setStatus: (status: StandupState['status']) => void
  isDarkMode: boolean
  toggleDarkMode: () => void
}

export const useStandupStore = create<StandupState>((set) => ({
  status: null,
  setStatus: (status) => set({ status }),
  isDarkMode: false,
  toggleDarkMode: () => set((state) => ({ isDarkMode: !state.isDarkMode })),
}))

在组件里直接用:

const { status, setStatus, isDarkMode, toggleDarkMode } = useStandupStore()

没有 Provider,没有 connect,没有 selector memoization。爽到飞起。

🚨 踩坑提醒:一开始我把 isDarkMode 存 localStorage,结果每次刷新都闪白屏。后来改成 SSR-safe 写法:

useEffect(() => {
  const saved = localStorage.getItem('darkMode') === 'true'
  if (saved) document.documentElement.classList.add('dark')
}, [])

这样首屏就不会 FOUC(Flash of Unstyled Content)了。


实时通信:Socket.IO 模拟器救我于水火

产品要求“实时看到队友状态”,但我不想真搭后端(懒)。于是写了个 mock server:

// utils/mockSocket.ts
type Status = 'done' | 'blocked' | 'thinking'

export class MockSocket {
  private listeners: Record<string, Function[]> = {}
  private fakeUsers = [
    { id: 'user1', name: 'Alice', status: 'done' as Status },
    { id: 'user2', name: 'Bob', status: 'blocked' as Status },
  ]

  on(event: string, callback: Function) {
    if (!this.listeners[event]) this.listeners[event] = []
    this.listeners[event].push(callback)
    
    // 模拟 2 秒后收到队友状态
    if (event === 'status:update') {
      setTimeout(() => {
        this.fakeUsers.forEach(user => {
          callback(user)
        })
      }, 2000)
    }
  }

  emit(event: string, data: any) {
    console.log('Emitting:', event, data)
    // 这里可以触发本地状态更新
  }
}

在组件里:

useEffect(() => {
  const socket = new MockSocket()
  socket.on('status:update', (user: any) => {
    // 更新 UI
  })

  return () => {
    // cleanup
  }
}, [])

等真要对接后端,只要把 MockSocket 换成 io('http://real-server') 就行。抽象层万岁


UI 与交互:细节决定用户体验

UI 我直接抄 ShadCN/ui 的 Button 和 Card,但加了点小心机:

  • 点击状态按钮有微动效(transition-transform hover:scale-105
  • 深色模式切换按钮带月亮/太阳图标
  • 手机端用 viewport 单位保证字体大小适中

最关键的——无障碍访问(a11y)不能忘!比如状态按钮:

<button
  aria-label={`Mark as ${status}`}
  className="..."
  onClick={() => setStatus(status)}
>
  {icon}
</button>

不然测试同事又要拿 Axe DevTools 扫描我,然后发邮件说“你的按钮没有 label”。


性能优化:Lighthouse 90+ 不是梦

为了达到 Lighthouse ≥90,我做了这几件事:

  1. 代码分割:路由级拆包(用 React.lazy
  2. 图片优化:所有 icon 用 SVG inline,避免 HTTP 请求
  3. 字体加载:用 font-display: swap 防止文字闪烁
  4. 关键 CSS 内联:Vite 插件自动处理
  5. 移除未使用代码:Tree-shaking 开启,import { Button } from 'shadcn' 而非全量引入

最骚的操作是——预加载队友状态数据。既然知道 mock 数据结构,我在 HTML 里直接塞了个 <script id="__INITIAL_STATE__">,首屏就能渲染,不用等 2 秒。

结果?Lighthouse 跑出来:

指标 分数
Performance 94
Accessibility 100
Best Practices 92
SEO 90

当场截图发朋友圈(虽然只有我妈点赞)。


部署上线:GitHub + Vercel,全自动流水线

代码托管在 GitHub(github.com/yourname/daily-standup),然后连 Vercel:

  1. 登录 Vercel,Import Project
  2. 选这个 repo
  3. 自动检测是 Vite 项目,配置都不用手动改
  4. 点 Deploy

30 秒后,daily-standup.vercel.app 可访问。连 CI/CD 脚本都不用写,学生党狂喜。

更绝的是,每次 push 到 main 分支,Vercel 自动 rebuild。上周五晚上我改了个 typo,commit 一推,喝口水回来就上线了。运维同事看了直呼内行(虽然我们公司运维还在用 Jenkins 跑 shell 脚本)。


求职视角:这个项目值不值得写进简历?

老实说,很多同学觉得“不就是个 TodoList 换皮吗”。但面试官真正在意的不是功能多复杂,而是:

  • 你是否考虑了工程化(类型安全、构建优化、错误边界)
  • 你是否关注用户体验(性能、a11y、响应式)
  • 你是否具备产品思维(为什么做这个?解决了什么痛点?)

我把这个项目包装成“提升远程团队站会效率的轻量工具”,放在 GitHub README 里写了背景、技术难点、性能数据。投简历时附上链接,比空洞地写“熟悉 React”有用十倍

上周面一家 startup,面试官直接打开我项目,问我:“WebSocket 断线怎么重连?”——还好我 mock 层留了扩展口,现场讲了指数退避重连策略,顺利过关。


最后几句真心话

从零搭一个现代化前端项目,最大的收获不是技术,而是对“完整链路”的理解。以前在公司,我只负责某个模块,打包部署都是运维的事。现在自己走一遍,才知道:

  • 一个 vite.config.ts 背后有多少坑
  • 为什么 Lighthouse 会扣你“未压缩图片”的分
  • 用户真的会在 iPhone SE 上打开你的页面

如果你也在等入职、或者准备跳槽,别光刷 LeetCode。花两周时间,认真做一个小而美的项目,放到 GitHub 上。它可能成为你简历上最亮眼的一行。

对了,项目地址我放 GitHub 了(匿了,但结构完全一致)。欢迎 star,更欢迎 issue —— 如果你发现我哪写错了,请务必骂我,毕竟程序员的进步,往往始于一次友好的 code review。


P.S. 写完这篇文章,Lo-fi 歌单刚好播到第 12 首。窗外天快亮了,而我的 Rust 学习计划还没开始……算了,先睡了,明天还得改简历。

评论 0

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