从零开始构建一个现代化前端项目:一个前测试工程师的“叛逃”实录

神奇的月亮
2025-12-17 08:27
阅读 1484

大家好,我是阿哲,坐标北京,每天早上8点雷打不动地坐在工位上(别问,问就是通勤1小时+早起强迫症)。三年前,我还是个天天写自动化脚本、和产品经理battle需求边界的测试工程师;如今,我成了团队里被叫“那个写React的”——没错,我成功“叛逃”到开发岗了。

为啥要写这篇文章?其实源于上周五晚上的一次线上事故。我们新做的活动页在iOS Safari上动画卡成PPT,用户投诉邮件刷屏,运维大哥半夜打电话问我:“你这前端是不是没测过真机?”那一刻我差点哭出来——毕竟,我可是从测试转过来的人啊! 怎么能让这种低级错误上线?

痛定思痛,我决定从头梳理一遍:如果现在让我从零开始搭一个现代前端项目,我会怎么做? 不是为了炫技,而是为了不再半夜被叫醒改bug。顺便,最近也在准备跳槽面试,发现很多面试题其实都藏在这些基建细节里。


起因:别再用create-react-app一键到底了

去年双11期间,我们团队接了个紧急需求:两周内上线一个带复杂交互动画的营销页。当时我图省事,直接 npx create-react-app my-cool-project,美滋滋地开始写组件。结果呢?

  • 动画卡顿,FPS掉到20
  • 打包后体积5MB,加载慢得像乌龟
  • 没有TypeScript,队友PR里全是any
  • 测试覆盖率?不存在的,CI一跑就挂

最离谱的是,某天产品经理说:“能不能加个暗黑模式?” 我打开代码一看:满屏的#333#fff硬编码,当场石化。

那一刻我悟了:现代化前端 ≠ 会写JSX。它是一套完整的工程体系,从代码规范到部署监控,缺一不可。


第一步:选型不是技术秀,是团队共识

很多人一上来就吹Vite比Webpack快10倍,但现实是:你得考虑团队接受度。我们组有5个前端,2个刚毕业,1个老IE兼容战士。最后我们定了这套组合:

技术栈 选择理由
React 18 团队熟悉,Concurrent Mode对动画友好
TypeScript 强制类型约束,减少低级错误(尤其对我这种转岗选手)
Vite 开发启动快,HMR秒级更新,提升幸福感
Tailwind CSS 原子化CSS,避免样式冲突,设计师给的设计稿能快速还原
Jest + RTL 测试库生态成熟,配合GitHub Actions做CI

吐槽一句:别信网上说的“Vite已经完全替代Webpack”。我们有个老项目依赖worker-loader,硬迁Vite花了三天,差点被运维祭天。

初始化命令长这样:

npm create vite@latest my-project -- --template react-ts
cd my-project
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

然后配一下tailwind.config.js

module.exports = {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {
      // 这里定义品牌色,方便后续切换主题
      colors: {
        primary: '#3B82F6',
        dark: '#111827'
      }
    },
  },
  plugins: [],
}

第二步:目录结构——别让代码变成“俄罗斯套娃”

以前做测试时,最烦看到前端代码结构乱成一锅粥:components里塞着API调用,utils里混着业务逻辑。现在轮到自己写,必须立规矩!

我们的结构长这样:

src/
├── assets/          # 静态资源(图片/SVG)
├── components/      # 通用UI组件(Button, Modal...)
├── features/        # 【关键!】按功能模块组织
│   └── animation-demo/
│       ├── api/     # 接口请求
│       ├── hooks/   # 自定义Hook
│       ├── types/   # TS类型定义
│       └── index.tsx
├── hooks/           # 全局Hook(useTheme, useMediaQuery...)
├── lib/             # 第三方库封装(比如axios实例)
├── routes/          # 路由配置
├── styles/          # 全局样式(重置/主题变量)
└── utils/           # 纯函数工具(日期格式化等)

为什么强调features目录?
因为面试官最爱问:“你们项目怎么组织代码的?” 答“按页面分”可能挂,答“按功能模块分”至少加分。更重要的是,提测时测试同学能快速定位改动范围——毕竟我懂他们的痛!


第三步:动画优化——别让交互变“幻灯片”

作为对前端动画有执念的人,这次我死磕性能。核心原则:能用CSS动画绝不用JS,能用transform/opacity绝不用top/left

比如这个卡片悬停效果:

// Bad: 直接修改width/height(触发重排)
const [isExpanded, setIsExpanded] = useState(false);
<div 
  style={{ 
    width: isExpanded ? '200px' : '100px',
    height: isExpanded ? '200px' : '100px'
  }}
/>

// Good: 用transform(仅触发合成)
<div className={clsx(
  "transition-transform duration-300",
  isExpanded && "scale-125"
)} />

更复杂的场景,比如路径动画,我用了framer-motion

import { motion } from 'framer-motion';

<motion.div
  initial={{ pathLength: 0 }}
  animate={{ pathLength: 1 }}
  transition={{ duration: 2, ease: "easeInOut" }}
>
  <svg>...</svg>
</motion.div>

性能监控不能少
vite.config.ts里加上:

import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [
    react({
      // 关键!开启React DevTools性能分析
      jsxImportSource: '@emotion/react',
    }),
  ],
});

然后浏览器按F12 → Performance面板录制,重点关注:

  • FPS是否稳定60
  • Main线程是否有长任务(Long Task > 50ms)
  • 内存是否持续增长(防内存泄漏)

第四步:TypeScript + Git Hooks——让低级错误死在本地

转开发后最大的感悟:TS不是增加负担,是减少救火次数。特别是对接Java后端时,他们的DTO字段名经常变,没有TS简直噩梦。

我们约定:

  • 所有API响应必须定义interface
  • 组件props必须严格类型
  • 禁用any(ESLint规则强制)

.eslintrc.cjs关键配置:

module.exports = {
  extends: [
    'plugin:@typescript-eslint/recommended',
    'plugin:react-hooks/recommended'
  ],
  rules: {
    '@typescript-eslint/no-explicit-any': 'error', // 禁用any!
    'react-hooks/rules-of-hooks': 'error'
  }
}

配合Git Hooks,在commit前自动检查:

# 安装husky + lint-staged
npx husky-init && npm install
npx husky add .husky/pre-commit "npx lint-staged"

package.json里加:

{
  "lint-staged": {
    "*.{js,ts,jsx,tsx}": ["eslint --fix", "prettier --write"]
  }
}

真实案例:上周同事提了个PR,把userId拼错成useId,TS直接报错,避免了一次线上事故。运维大哥后来请我喝了杯瑞幸——这感觉,比当年发现重大bug还爽!


第五步:测试策略——前测试人的执念

很多人觉得前端不用写测试,但经历过凌晨三点修bug的人懂:自动化测试是深夜安眠药

我们的策略分三层:

测试类型 工具 覆盖场景 目标覆盖率
单元测试 Jest 工具函数/纯逻辑 ≥80%
组件测试 React Testing Library UI交互/状态变化 ≥70%
E2E测试 Cypress 核心用户流程(登录→下单) ≥50%

举个RTL测试例子(测试暗黑模式切换):

// DarkModeToggle.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import DarkModeToggle from './DarkModeToggle';

test('toggles dark mode when clicked', () => {
  render(<DarkModeToggle />);
  
  const toggleBtn = screen.getByRole('button');
  fireEvent.click(toggleBtn);
  
  // 检查body是否添加了dark类
  expect(document.body).toHaveClass('dark');
});

CI集成
在GitHub仓库里配.github/workflows/test.yml

name: CI
on: [push]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - run: npm ci
      - run: npm test -- --coverage
      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v3

每次PR都会跑测试,覆盖率低于阈值直接block合并。产品经理再也不敢说“先上线再测”了(手动狗头)。


第六步:部署与监控——别让上线变“开盲盒”

最后一步最容易翻车。我们用GitHub Pages做预览,Vercel做生产部署:

# 预览环境(PR自动部署)
npm run build
npx gh-pages -d dist

# 生产环境(连Vercel账号自动同步main分支)
# 在Vercel后台点几下就行,真香!

但光部署不够,得监控!集成Sentry

// main.tsx
import * as Sentry from "@sentry/react";

Sentry.init({
  dsn: "YOUR_DSN",
  integrations: [new BrowserTracing()],
  tracesSampleRate: 1.0,
});

createRoot(document.getElementById('root')!).render(
  <Sentry.ErrorBoundary fallback={<div>崩溃了,但我在修!</div>}>
    <App />
  </Sentry.ErrorBoundary>
);

上周那个Safari动画卡顿问题,就是Sentry上报的异常堆栈帮我们定位到:某个CSS属性不支持will-change。加个兼容性前缀就解决了。


写在最后:从测试视角看前端工程化

回看这三年,从写测试用例到搭建完整前端体系,最大的收获不是技术,而是思维转变

  • 测试思维:写代码前先想“怎么测”,自然会写出高内聚低耦合的代码
  • 用户视角:动画卡顿?加载慢?这些在测试时都是“缺陷”,开发时就要规避
  • 协作意识:清晰的代码结构、完善的文档,是对团队最大的尊重

最近面试被问到:“你觉得前端工程化最重要的是什么?”
我答:“让项目在半年后还能愉快地维护,而不是变成祖传代码。”

如果你也在从测试转向开发,或者正被混乱的项目折磨,不妨试试这套方案。毕竟,我们写代码不是为了应付deadline,而是为了少加班(虽然往往事与愿违 😅)。

彩蛋:文中的所有配置我都整理到了GitHub,搜modern-frontend-boilerplate就能找到。Star不重要,但要是能帮你少熬一次夜,那这波血赚!


作者:阿哲,前测试工程师,现React“搬砖人”,坚信“好的前端项目应该像乐高——拆得开,拼得稳”。
日常:8点开工,9点喝第一杯咖啡,18点准时跑路(除非线上炸了)。
座右铭:Code is poetry, but bugs are reality.

评论 0

最热最新
暂无评论
神奇的月亮Lv.1
0
影响力
0
文章
0
粉丝