从零开始构建一个现代化前端项目:一个前测试工程师的“叛逃”实录
大家好,我是阿哲,坐标北京,每天早上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