从零开始构建一个现代化前端项目:一个刷题党兼爬虫爱好者的实战笔记
“这项目下周一上线,后端接口周五才给,UI稿还在改。”
—— 我们的产品经理,上周五下午4:58发来的钉钉消息。
大家好,我是 Claude Code 的早期尝鲜用户(对,就是那个命令行里能直接调 AI 写代码的工具),日常 VSCode 插件装到卡顿,最近一边在公司应付各种“紧急需求”,一边晚上偷偷刷 LeetCode 准备跳槽。说真的,现在大环境卷成麻花了,光会写业务组件真不够看——面试官开口就是“你做过什么性能优化?”、“有没有独立搭建过工程体系?”
于是上个月,我接了个内部小活儿:给运营团队搞个竞品数据监控面板。需求听着简单:“每天抓点对手家的商品价格、活动文案,展示成图表就行”。但老板补了一句:“要快、要稳、还要能加新站点”——典型的“既要又要还要”。
没办法,只能自己从零搭一套现代化前端项目了。这篇文章就记录下整个过程,不讲虚的,全是我在深夜咖啡因超标、npm install 卡住、Webpack 报错时踩出来的坑。如果你也在准备求职、想证明自己有工程能力,或者单纯想看看怎么把 React + 爬虫 + 运营需求缝合成一个能跑的系统,这篇应该对你有用。
起手式:别再用 create-react-app 了!
我知道很多人(包括半年前的我)一建项目就 npx create-react-app my-app,然后快乐地写 JSX。但在真实工作场景中,CRA 就像共享单车——短途代步还行,真要拉货、越野、改装?门都没有。
这次项目有几个硬性要求:
- 首屏加载 < 1.5s(运营小姐姐说“等三秒我就关了”)
- 支持动态新增爬虫源(明天可能要监控拼多多,后天可能是抖音小店)
- 代码要能过 lint + type check(我们组刚被线上事故教育过)
- 部署要一键化(运维小哥说了,“别让我手动配 Nginx”)
所以我直接上了 Vite + TypeScript + React 18 的组合。Vite 的 HMR 快到飞起,TypeScript 防止我写出 undefined is not a function 的史诗级 Bug,React 18 的并发特性虽然用不上,但简历上写起来好看啊(求职加分项懂不懂!)。
初始化命令行走起:
# 我的终端 alias:cc = code . (别问,问就是懒)
npm create vite@latest competitor-dashboard -- --template react-ts
cd competitor-dashboard
npm install
npm run dev
不到 10 秒,本地开发服务器跑起来了。对比以前 Webpack 动辄 30 秒的启动时间,感动得差点给尤雨溪寄锦旗。
目录结构:别让 src 变成垃圾场
很多新人(包括我)一开始把所有文件塞进 src/components,结果两周后连自己都找不到 ProductCardV2Final.tsx 到底是不是最新版。这次我直接按 功能域 + 技术分层 拆:
src/
├── app/ # 应用入口、路由、全局状态
├── features/ # 业务功能模块(核心!)
│ ├── price-monitor/
│ └── campaign-tracker/
├── shared/ # 可复用的 UI 组件、hooks、utils
├── entities/ # 数据模型定义(对标后端的 domain)
└── infra/ # 基础设施:API client, crawler adapters
这种结构的好处是:运营需求变更时,我只需要动 features/price-monitor,不会污染其他模块。而且面试时画架构图也显得很专业(笑)。
爬虫数据怎么喂给前端?别天真了!
这里有个关键点:前端不能直接写爬虫!你以为 fetch('https://competitor.com') 就能拿到数据?醒醒,CORS 和反爬机制会让你哭着删 repo。
我的方案是:后端提供 API,前端只负责消费。但作为全栈打工人,后端我也得搞。不过这不是重点,前端要关注的是如何设计数据接口契约。
我让后端返回的数据长这样:
// entities/price-data.ts
export interface CompetitorPrice {
id: string;
productName: string;
currentPrice: number;
originalPrice?: number;
platform: 'taobao' | 'jd' | 'pinduoduo'; // 枚举!防止前端瞎写
crawledAt: string; // ISO 8601 格式
}
前端通过 infra/api-client.ts 统一调用:
// infra/api-client.ts
const API_BASE = import.meta.env.VITE_API_URL;
export const fetchPrices = async (): Promise<CompetitorPrice[]> => {
const res = await fetch(`${API_BASE}/prices`);
if (!res.ok) throw new Error('Failed to fetch prices');
return res.json();
};
💡 求职 tip:在简历里写“设计并实现前后端数据接口规范”,比“调用 API 展示数据”听起来高级多了。
性能优化:运营页面也要丝滑!
运营小姐姐打开页面第一句话:“怎么转圈这么久?”。我一看 Lighthouse,Performance 才 45 分……赶紧抢救。
1. 代码分割(Code Splitting)
首页只加载必要组件,图表库(用了 ECharts)和表格(Ant Design Table)全部动态导入:
// features/price-monitor/PriceDashboard.tsx
import { Suspense, lazy } from 'react';
const PriceChart = lazy(() => import('./PriceChart'));
const PriceTable = lazy(() => import('./PriceTable'));
export default function PriceDashboard() {
return (
<div>
<Suspense fallback={<div>Loading chart...</div>}>
<PriceMap />
</Suspense>
<Suspense fallback={<Spin />}>
<PriceTable />
</Suspense>
</div>
);
}
2. 虚拟滚动(别让 1000 行表格卡死浏览器)
运营要展示最近 30 天所有商品价格变动,动辄上千条。直接渲染?Chrome 内存直接爆红。我祭出了 react-virtual:
import { useVirtualizer } from '@tanstack/react-virtual';
const PriceTable = ({ data }: { data: CompetitorPrice[] }) => {
const parentRef = useRef<HTMLDivElement>(null);
const rowVirtualizer = useVirtualizer({
count: data.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 60,
overscan: 5,
});
return (
<div ref={parentRef} style={{ height: '500px', overflow: 'auto' }}>
<div style={{ height: `${rowVirtualizer.getTotalSize()}px` }}>
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
const item = data[virtualRow.index];
return (
<div
key={item.id}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
transform: `translateY(${virtualRow.start}px)`,
}}
>
{/* 渲染单行 */}
</div>
);
})}
</div>
</div>
);
};
效果:内存占用从 800MB 降到 120MB,滚动如德芙般丝滑。
3. 缓存策略:别让运营每次刷新都等 10 秒
用 SWR 做数据缓存 + 自动重试:
import useSWR from 'swr';
const { data, error } = useSWR('/api/prices', fetcher, {
refreshInterval: 60000, // 每分钟自动刷新
revalidateOnFocus: false, // 切回来不重刷(省流量)
dedupingInterval: 10000, // 10 秒内重复请求去重
});
调试技巧:命令行党的快乐
作为 Claude Code 用户,我习惯在终端里解决问题。分享几个救命命令:
快速查看打包体积:
npx source-map-explorer dist/assets/*.js发现 moment.js 占了 300KB?立刻换成 dayjs。
模拟慢网速: Chrome DevTools 的 Network Throttling 是基础,但更狠的是:
# 在 Vite 启动时加延迟 export VITE_MOCK_DELAY=2000 && npm run dev然后在 api-client 里加个
setTimeout,逼自己写 loading 状态。类型检查自动化: 我的 package.json scripts:
{ "type-check": "tsc --noEmit", "lint": "eslint . --ext ts,tsx --fix", "pre-commit": "npm run type-check && npm run lint" }Git 提交前自动跑,避免把
any提到主干——上次这么干被 leader 当众嘲笑了。
部署:让运维小哥爱上你
最后一步,部署。我们用 GitHub Actions + Vercel(免费额度够内部工具用)。
.github/workflows/deploy.yml 关键配置:
name: Deploy Dashboard
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- run: npm ci
- run: npm run build
- uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: '--prod'
上线后,运维小哥居然给我点了赞:“终于不用我手动 scp 了”。那一刻,我觉得值了。
成果 & 反思
项目上线三周,运营团队每天用它盯竞品,老板说“这个可视化做得不错”。更重要的是——我把整个搭建过程整理成文档,附在了跳槽简历的项目经验里。
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 首屏加载 | 3.2s | 1.1s |
| Bundle Size | 2.1MB | 780KB |
| Lighthouse Performance | 45 | 92 |
当然,过程中也翻过车:
- 有次忘记
.env文件加到.gitignore,把测试 API 密钥提交了,被安全扫描告警; - 图表颜色用了
#f00,运营说“红色看着像降价失败”,被迫重做主题色; - 最离谱的是,爬虫 IP 被淘宝封了,临时加了代理池,差点没赶上 deadline。
但正是这些坑,让我在面试时能侃侃而谈:“我遇到过 XX 问题,通过 XX 方案解决,最终指标提升 XX%”。
写在最后
从零搭建一个现代化前端项目,远不止 npm install react 那么简单。它考验的是你对工程化、性能、协作、甚至产品思维的综合理解。尤其在当下求职市场,能独立交付完整项目的人,永远比只会写 TodoList 的人吃香。
如果你也在刷题准备跳槽,不妨花一周时间,照着本文思路搭个小项目。不用多复杂,但一定要有痛点、有解法、有数据。面试时甩出 GitHub 链接和 Lighthouse 报告,HR 眼睛都会亮。
对了,Claude Code 最近出了新功能,能根据你的 commit message 自动生成 changelog。我正打算用它来管理这个监控面板的版本更新——毕竟,打工人的终极梦想,就是让机器干活,自己摸鱼刷题啊。
(完)
P.S. 本文所有代码已脱敏开源在 GitHub,搜
competitor-dashboard-demo就能找到。Star 不指望,但要是能帮你拿下面试 offer,记得请我喝杯瑞幸。

评论 0