从零开始构建一个现代化前端项目:我的踩坑实录

Markdown诗人
2026-01-05 16:52
阅读 1749

上周五晚上九点半,我刚把最后一行 Rust 代码跑通,正准备关机回家,钉钉突然弹出一条消息:“老张,新项目下周启动,前端你来搭架子。” 我心里一咯噔——又来了。这已经是今年第三次让我从零搭项目了。在这家公司待了三年多,眼看35岁生日快到了,每天早上八点雷打不动坐在工位上敲代码的日子,也不知道还能持续多久。最近其实已经在偷偷更新简历,打算换个环境,但手头的活儿总得干完不是?

说起来,这次的需求还真不简单:要搞一个支持实时协作的文档编辑平台,UI 要现代、响应快、兼容性好,还得能快速迭代。产品经理画了一堆高保真原型,运维说“最好能一键部署”,测试同事则幽幽地补了一句:“上次那个内存泄漏,线上跑了三天才被发现……”

行吧,既然躲不过,那就干。今天这篇就记录一下我从零搭建这个项目的全过程——不是教科书式的完美流程,而是真实世界里一个老程序员边踩坑边摸索出来的路子。


别再用 create-react-app 闭着眼睛开工了

很多人一听说“新建前端项目”,第一反应就是:

npx create-react-app my-awesome-app

说实话,三年前我也这么干。但现在?除非是临时 demo,否则真不敢这么糙。为啥?因为 CRA 虽然开箱即用,但它把配置全藏起来了——你不知道 Webpack 干了啥,Babel 怎么转的,连个自定义 ESLint 规则都得 eject 出来改,一旦 eject 基本就回不去了。

这次我决定自己搭脚手架。目标很明确:

  • 支持 TypeScript(类型安全太重要了)
  • 模块化 CSS(用 CSS Modules 或 Tailwind)
  • 自动代码格式化 + Lint
  • 快速本地开发(HMR 必须丝滑)
  • 生产构建优化(Tree-shaking、Code Splitting)

我选了 Vite。不是因为它新,而是因为它真的快。冷启动 200ms,热更新基本无感。而且配置文件清晰,不像 Webpack 那种层层嵌套的噩梦。

初始化项目:

npm create vite@latest doc-collab -- --template react-ts
cd doc-collab
npm install

然后立刻装上我常用的工具链:

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

配置 tailwind.config.js

/** @type {import('tailwindcss').Config} */
export default {
  content: [
    "./index.html",
    "./src/**/*.{js,jsx,ts,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

再在 src/index.css 里加三行:

@tailwind base;
@tailwind components;
@tailwind utilities;

搞定。现在我可以写 <div className="p-4 bg-blue-100 rounded-lg">Hello</div> 这种清爽的类名了,不用再和 CSS 文件里的命名空间打架。


Git 和 GitHub:别等代码写完了才想起来初始化

很多新人(包括当年的我)有个坏习惯:先疯狂写代码,等项目差不多了再 git init。结果就是第一次 commit 就几千行,后续根本没法追溯。

这次我从第一天就建好了 GitHub 仓库。名字叫 doc-collab-frontend,描述写清楚用途,加了 .gitignore(用 gitignore.io 生成的 React + Node 模板),还写了初步的 README.md

最关键的是,我立刻配置了 分支策略

  • main 分支保护,禁止直接 push
  • 所有功能走 feature/xxx 分支
  • PR 合并需至少一人 review

别小看这点,上周另一个组就因为有人直接推到 main,把测试环境搞崩了,运维在群里咆哮了半小时。

我还顺手加了两个 GitHub Actions workflow:

  1. CI 流水线:每次 push 自动跑 lint、test、build
  2. 预发布部署:PR 合并后自动部署到 preview 环境(用 Vercel)

.github/workflows/ci.yml 片段:

name: CI
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 18
      - run: npm ci
      - run: npm run lint
      - run: npm run build

这样,只要有人提 PR,我就知道代码能不能跑起来。省得等到上线前一天才发现“哦,原来你的代码在 Windows 上跑不了”。


组件设计:别让 UI 变成意大利面条

这次项目要求高复用性。比如“用户头像”组件,要在文档列表、评论区、协作面板里反复出现。如果每个地方都 copy-paste 一段 <img src={...} className="w-8 h-8 rounded-full" />,那以后产品经理说“头像要加 hover 效果”,你就得改十处。

我的做法是:原子化设计 + Storybook 预览

先装 Storybook:

npx storybook init

然后建一个 src/components/ui/Avatar.tsx

// Avatar.tsx
import { FC } from 'react';

interface AvatarProps {
  src?: string;
  alt?: string;
  size?: 'sm' | 'md' | 'lg';
  className?: string;
}

const SIZE_MAP = {
  sm: 'w-6 h-6',
  md: 'w-8 h-8',
  lg: 'w-12 h-12',
};

export const Avatar: FC<AvatarProps> = ({
  src,
  alt = 'User',
  size = 'md',
  className = '',
}) => {
  return (
    <img
      src={src || '/default-avatar.png'}
      alt={alt}
      className={`${SIZE_MAP[size]} rounded-full object-cover ${className}`}
    />
  );
};

再写对应的 Avatar.stories.tsx

import { Avatar } from './Avatar';

export default {
  title: 'UI/Avatar',
  component: Avatar,
};

export const Small = () => <Avatar size="sm" />;
export const Medium = () => <Avatar size="md" />;
export const Large = () => <Avatar size="lg" />;

npm run storybook,就能在浏览器里看到所有状态。测试同学甚至可以直接拿这个链接确认 UI 是否符合设计稿——再也不用等我部署到测试环境。


性能不是玄学:监控和优化要趁早

去年双11期间,我们一个页面首屏加载花了 4.8 秒,用户跳出率飙升。事后复盘发现,光一个图表库就占了 1.2MB,而且还是同步加载的。

这次我从第一天就加了性能兜底措施:

  1. 路由级代码分割
    用 React.lazy + Suspense 拆分路由:

    const EditorPage = lazy(() => import('./pages/EditorPage'));
    const Dashboard = lazy(() => import('./pages/Dashboard'));
    
    // 在 Router 里
    <Routes>
      <Route path="/" element={
        <Suspense fallback={<div>Loading...</div>}>
          <Dashboard />
        </Suspense>
      } />
    </Routes>
    
  2. 图片懒加载 + WebP 格式
    所有静态资源走 CDN,且自动转 WebP。本地开发时用 vite-plugin-imagemin 压缩。

  3. Performance Budget
    vite.config.ts 里加了个插件,限制 bundle 大小:

    import { visualizer } from 'rollup-plugin-visualizer';
    
    export default defineConfig({
      plugins: [
        // ...其他插件
        visualizer({ open: false }), // 生成 stats.html
      ],
      build: {
        rollupOptions: {
          // 如果 vendor chunk > 500kb 就报警
        }
      }
    });
    

上线前,我会跑 npm run build -- --report,看看有没有哪个依赖偷偷膨胀了。


调试技巧:Console.log 是最后的选择

老程序员都知道,靠 console.log 调异步逻辑,迟早会疯。这次我全程用 React DevTools + Vite 的 HMR 状态保留

比如处理 WebSocket 实时协作时,状态更新很频繁。我在组件里加了:

useDebugValue(collaborators.length); // 在 DevTools 里直接显示人数

配合 Redux DevTools(如果用了状态管理),可以回放每一步操作。上周五就靠这个定位了一个“光标位置错乱”的 bug——原来是某个 action 的 payload 缺了 timestamp 字段,导致合并冲突时排序错误。

另外,Vite 的 HMR 默认会保留 React 组件状态。这意味着我改个样式,输入框里的文字不会丢;改个函数逻辑,当前打开的文档也不会重置。这对开发体验提升巨大。


最终效果:从混乱到可控

项目上线两周了,目前运行平稳。关键指标如下:

指标 优化前 优化后
首屏加载时间 3.2s 1.1s
Bundle 大小 (gzip) 2.1MB 890KB
CI 构建时间 2m 15s 42s

最让我欣慰的是,新来的实习生也能快速上手——因为项目结构清晰、文档齐全、自动化到位。他昨天还跟我说:“哥,你这架子搭得真舒服,比上家公司强多了。”


写在最后:35岁的程序员还在折腾,图个啥?

可能有人觉得,都这岁数了,何必折腾这些?用现成的模板不好吗?

但我觉得,保持对技术的好奇和掌控感,才是程序员不被淘汰的关键。最近研究 Rust 也是这个原因——不是为了转行,而是想理解系统底层怎么运作。前端越来越复杂,如果不深挖原理,迟早会被框架牵着鼻子走。

这次从零搭项目,虽然累,但每一步都踏实。GitHub 提交记录清清楚楚,代码结构经得起 review,性能数据拿得出手——这才是一个成熟工程师该交付的东西。

至于跳槽?简历已经投出去了。不管结果如何,至少这个项目,我能挺直腰板说:“这是我亲手搭的,没糊弄。”

(完)

评论 0

最热最新
暂无评论
Markdown诗人Lv.1
0
影响力
0
文章
0
粉丝