从零搭建前端项目,一个文科生的血泪实战
上个月底,我刚入职新公司两个月,老板突然在周会上扔来一个需求:“小张,下个季度我们要上线一个新的运营活动平台,你来负责前端架构搭建。”我当时心里咯噔一下——作为一个非科班出身、靠自学转码的文科生,虽然已经能熟练写 React 组件了,但“从零搭建项目”这种活儿,听起来就带着一股“线上崩了你背锅”的压迫感。
更扎心的是,这个平台要支撑全年的营销活动,比如618、双11那种流量洪峰。产品经理还美其名曰:“轻量级 MVP,先跑起来再说。”可我知道,一旦上线,运营同学会像蝗虫一样涌进来配置各种弹窗、抽奖、倒计时……代码要是烂,后期维护就是地狱。
但没办法,饭碗要紧。而且说实话,我也想证明给团队看看:文科生也能写出干净、可维护、扛得住压力的前端工程。
于是,我花了两周时间,边听 Billie Eilish 的新专辑边折腾,最终搭出了一套现代化前端项目骨架。今天就来聊聊这段“从零开始”的实战经验,顺便吐吐槽、踩踩坑。
技术选型:不是越新越好,而是“够用+好改”
很多新人(包括半年前的我)一听说“现代化”,立马冲去 GitHub 看 Star 数最高的框架,结果搭完发现团队没人会修 Bug,或者和现有系统完全不兼容。这次我学乖了:技术选型的核心不是炫技,而是“可持续交付”。
我们的核心诉求很明确:
- 快速开发运营活动页(大量动态配置)
- 支持微前端(未来可能接入其他业务线)
- 代码可读性高(毕竟我经常被叫去解释为什么某个按钮没对齐)
- 构建速度不能太慢(测试同学等不及)
下面是我对比的几个关键环节:
框架:React vs Vue3
| 维度 | React (18+) | Vue3 |
|---|---|---|
| 团队熟悉度 | 70% 用过 | 30% 用过 |
| 生态丰富度 | 高(尤其 UI 库) | 中高 |
| 学习曲线 | JSX 对新手稍陡 | 模板语法更直观 |
| 微前端支持 | 完善(qiankun 等) | 也支持,但社区案例少些 |
我们团队之前项目都是 React,加上 Ant Design Pro 有现成的运营后台模板,最终还是选了 React 18 + TypeScript。虽然我个人觉得 Vue 的 <script setup> 写起来更爽,但“团队一致性”比个人喜好重要多了——别忘了,我是唯一一个非科班,得降低协作成本。
构建工具:Vite 还是 Webpack?
去年我还在用 Create React App(Webpack),每次改一行 CSS 都要等 10 秒热更新,简直想砸键盘。这次果断试了 Vite。
实测效果:
- 启动时间:Webpack 25s → Vite 1.2s
- HMR 更新:Webpack 3-5s → Vite <300ms
而且 Vite 对 TypeScript、CSS Modules、SVG Sprite 的原生支持非常友好,几乎不用配。唯一的小坑是:某些老插件(比如 html-webpack-plugin 的替代品)需要找 Vite 版本,不过 GitHub 上基本都有人写了。
状态管理:Zustand 走起!
以前我迷信 Redux,写一堆 action、reducer、selector,结果运营页面状态其实很扁平——要么是活动配置,要么是用户行为记录。Redux 那套仪式感太重了。
这次用了 Zustand,几行代码搞定全局状态:
// stores/activityStore.ts
import { create } from 'zustanding';
interface ActivityState {
config: ActivityConfig | null;
setConfig: (config: ActivityConfig) => void;
}
export const useActivityStore = create<ActivityState>((set) => ({
config: null,
setConfig: (config) => set({ config }),
}));
用的时候直接 const { config } = useActivityStore();,清爽得像喝冰阔落。而且它自动做 memoization,性能完全不用担心。
UI 组件库:Ant Design Pro 还是自己造轮子?
运营后台需要表格、表单、图表、权限控制……如果从零写,我估计得加班到春节。好在 Ant Design Pro 提供了完整的中后台解决方案,连国际化、动态菜单都配好了。
但!千万别直接用默认主题色。上次产品经理看到蓝色后台,说“不够喜庆”,硬让我改成红色。结果全局覆盖样式搞了两天,差点抑郁。现在我的建议是:初期用 Pro,但尽早抽离主题配置。
目录结构:为“未来接手的人”写代码
我见过太多项目,src 里堆了 50 个文件,名字全是 utils1.js、helper_new_v2.js……作为注重可读性的文科生,我坚持一点:代码是写给人看的,机器只是顺便执行一下。
最终目录长这样:
src/
├── assets/ # 静态资源
├── components/ # 通用组件(Button, Modal)
├── features/ # 业务功能模块(每个活动一个文件夹)
│ ├── lucky-draw/ # 抽奖活动
│ └── countdown/ # 倒计时
├── hooks/ # 自定义 Hook
├── layouts/ # 布局
├── pages/ # 路由页面
├── services/ # API 请求
├── stores/ # 状态管理
├── styles/ # 全局样式 & 主题
├── types/ # TypeScript 类型定义
└── utils/ # 工具函数(带单元测试!)
特别说明:
features/是亮点!每个运营活动独立封装,互不干扰。运营同学要加新玩法?直接新建一个文件夹,不影响旧逻辑。- 所有 API 调用走
services/,统一处理 loading、error、mock。 utils/里的函数必须有 JSDoc 注释,比如:
/**
* 格式化金额,保留两位小数,千分位逗号分隔
* @example formatMoney(123456.789) → "123,456.79"
*/
export const formatMoney = (num: number): string => {
// ...
};
性能优化:别让运营拖垮用户体验
运营最爱搞“花里胡哨”的动画和图片,但用户可不管这些——他们只关心“点按钮有没有反应”。
我做了三件事:
1. 图片懒加载 + WebP
用 react-intersection-observer 实现懒加载,配合后端返回 WebP 格式。体积平均减少 40%,首屏加载快了不少。
import { useInView } from 'react-intersection-observer';
const LazyImage = ({ src, alt }) => {
const [ref, inView] = useInView();
return (
<img
ref={ref}
src={inView ? src : placeholder}
alt={alt}
loading="lazy"
/>
);
};
2. 动态导入运营模块
不是所有活动都要一开始就加载。用 React.lazy + Suspense 按需加载:
const LuckyDraw = lazy(() => import('@/features/lucky-draw'));
function App() {
return (
<Suspense fallback={<Loading />}>
{currentActivity === 'lucky' && <LuckyDraw />}
</Suspense>
);
}
3. 防抖提交
运营页面常有“保存配置”按钮。我加了防抖,避免手滑连点十次,把后端打挂:
const debouncedSave = useMemo(
() => debounce((data) => api.saveConfig(data), 500),
[]
);
GitHub 工作流:别让 PR 变战场
我们团队强制要求:所有代码必须通过 PR 合并,且至少一人 review。
为了减少扯皮,我在 .github/PULL_REQUEST_TEMPLATE.md 里写了清晰的 checklist:
- [ ] 自测通过(包括边界 case)
- [ ] 单元测试覆盖率 ≥80%
- [ ] 无 console.log
- [ ] 文案已交给产品确认
- [ ] 性能影响评估(如有)
另外,用 ESLint + Prettier + Husky 在提交前自动格式化,避免“空格党”和“制表符党”打架。文科生表示:统一风格,世界和平。
真实踩坑:那些文档不会告诉你的事
Vite + Ant Design Pro 的图标问题
Pro 默认用@ant-design/icons,但 Vite 不识别动态导入。解决办法:显式引入图标,或者用vite-plugin-antd-icon。运营配置的 JSON Schema 校验
有次运营填了个非法的正则表达式,导致整个页面白屏。后来我在zod里加了严格校验:const ActivityConfigSchema = z.object({ title: z.string().min(1), regex: z.string().regex(/^\/.*\/[gimuy]*$/), // 必须是合法正则字符串 });Safari 的 Date 陷阱
“2024-06-18” 这种日期字符串在 Safari 里解析成 Invalid Date!必须转成 “2024/06/18” 或用dayjs处理。文科生第一次遇到时,真的以为是电脑坏了。
效果与反思
上线两周,支撑了三次小型运营活动,0 P0 事故。最让我自豪的是:另一个同事接手一个新活动模块时,只用了半天就跑通了全流程——他说:“目录太清晰了,像读小说一样顺。”
当然,还有很多不足。比如还没接入前端监控(Sentry)、自动化部署流程也手动。但作为非科班,能在两个月内交出这样的答卷,我已经挺直腰杆了。
最后想对和我一样的转码朋友说:文科生的共情力和细节控,其实是前端的巨大优势。我们更懂用户怎么想,更在意界面是否“顺眼”,更愿意写注释——这些,才是好代码的灵魂。
GitHub 仓库我脱敏后会开源(等老板批准),欢迎来 star 和吐槽。毕竟,写代码的路上,谁还不是一边崩溃一边进步呢?
写完这篇博客,已经是凌晨一点。耳机里 Billie Eilish 唱着:“I’m not your friend or anything… damn.”
我笑了笑,按下保存,关掉编辑器。明天还要和产品经理 battle 一个“能不能加个呼吸灯效果”的需求。

评论 0