从零开始构建一个现代化前端项目:一个老派手写党的“妥协”实践
上周五晚上十点,我正对着 MacBook Pro 的 Terminal 发呆,咖啡已经凉了第三杯。产品经理又在群里 @ 全体成员:“这个新后台系统下周三必须上线,UI 已经锁定了,你们看着办。”——熟悉的配方,熟悉的味道。
我在这家公司待了三年多,眼看着前端技术栈从 jQuery + Bootstrap 换到 Vue,再换到 React,中间还夹杂着无数个“重构”的 flag。作为团队里出了名的“手写代码原教旨主义者”,我一直坚持:能手敲绝不 copy,能理解绝不盲用。但现实是,最近面试了几家心仪公司,发现“现代化前端工程能力”成了硬通货——光会写组件不够,还得会搭脚手架、搞 CI/CD、配 ESLint/Prettier、玩转 Vite……得,这不就是逼我这个保守派“与时俱进”嘛?
于是,借着这次新项目的契机,我决定:从零开始,亲手搭建一个真正现代化的 React 前端项目。不靠 Create React App(虽然它香),不用 Next.js(虽然它快),就用最基础的工具链,一点一点拼起来。以下就是我的“血泪”实录。
起手式:别再被 Webpack 折磨了
坦白讲,以前我对 Webpack 又爱又恨。爱它强大,恨它配置像天书。记得去年双11前,因为一个 loader 配置错了,导致打包后 CSS 变量失效,线上样式全崩,运维大哥半夜打电话骂我“前端狗”。那之后我就发誓:如果还有机会从头搭项目,一定要选更轻量、更快的工具。
所以这次,我毫不犹豫地选了 Vite。
npm create vite@latest my-modern-app -- --template react-ts
cd my-modern-app
npm install
不到 10 秒,项目骨架就立起来了。npm run dev 一跑,HMR(热更新)快得飞起,改一行代码,浏览器几乎瞬间刷新。对比以前 Webpack 动不动 20 秒的启动时间,我直呼“真香”。
小贴士:Vite 利用原生 ES 模块(ESM)在开发阶段直接 serve 源码,无需打包,所以快如闪电。生产构建则用 Rollup,小巧高效。
代码规范:别让队友骂你“格式杀手”
我们团队有个不成文的规定:谁提交的代码没格式化,谁请奶茶。以前我靠手动缩进+肉眼对齐,结果某次被同事吐槽:“你这代码对齐是靠玄学吗?”后来痛定思痛,引入 Prettier + ESLint 组合拳。
关键在于:让工具自动格式化,而不是靠人自律。
我在 .eslintrc.cjs 里启用了 React Hooks 规则和 TypeScript 支持:
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
'prettier' // 必须放最后,覆盖其他规则
],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': 'warn',
}
}
配合 prettier.config.js:
module.exports = {
semi: true,
singleQuote: true,
tabWidth: 2,
trailingComma: 'es5',
printWidth: 100
}
再在 VS Code 装上 Prettier 插件,设置保存时自动格式化。从此,代码风格统一,PR 里再也不用争论“该不该加分号”这种哲学问题了。
状态管理:别一上来就上 Redux
很多新人一听说“状态管理”,立刻想到 Redux。但说实话,对于大多数中后台项目,React Context + useReducer 就够了。除非你有复杂的中间件需求(比如日志、持久化),否则 Redux 纯属过度设计。
我在项目里封装了一个简单的 AppProvider:
// context/AppContext.tsx
import { createContext, useContext, useReducer } from 'react';
type AppState = {
user: { name: string } | null;
theme: 'light' | 'dark';
};
type Action =
| { type: 'SET_USER'; payload: AppState['user'] }
| { type: 'TOGGLE_THEME' };
const initialState: AppState = {
user: null,
theme: 'light',
};
function appReducer(state: AppState, action: Action): AppState {
switch (action.type) {
case 'SET_USER':
return { ...state, user: action.payload };
case 'TOGGLE_THEME':
return { ...state, theme: state.theme === 'light' ? 'dark' : 'light' };
default:
return state;
}
}
const AppContext = createContext<{
state: AppState;
dispatch: React.Dispatch<Action>;
}>({ state: initialState, dispatch: () => {} });
export function AppProvider({ children }: { children: React.ReactNode }) {
const [state, dispatch] = useReducer(appReducer, initialState);
return (
<AppContext.Provider value={{ state, dispatch }}>
{children}
</AppContext.Provider>
);
}
export const useApp = () => useContext(AppContext);
用起来也简单:
// Header.tsx
const { state, dispatch } = useApp();
<button onClick={() => dispatch({ type: 'TOGGLE_THEME' })}>
切换主题 ({state.theme})
</button>
清爽、无依赖、类型安全。等哪天真需要 Redux Toolkit 了,再迁也不迟。
UI 组件库:别重复造轮子,但要懂原理
我们用的是 Ant Design,理由很现实:产品经理喜欢它的默认样式,测试同学熟悉它的交互行为,省沟通成本。
但我不只是 npm install antd 就完事。我做了两件事:
- 按需加载:避免打包进整个 Antd。
- 自定义主题:覆盖默认色值,匹配公司品牌。
Vite 配置如下(借助 vite-plugin-antd 或手动配置):
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig({
plugins: [react()],
css: {
preprocessorOptions: {
less: {
javascriptEnabled: true,
additionalData: `@import "${resolve(__dirname, 'src/styles/variables.less')}";`
}
}
},
build: {
rollupOptions: {
output: {
manualChunks: {
antd: ['antd'],
vendor: ['react', 'react-dom']
}
}
}
}
});
然后在 variables.less 里重写 Antd 变量:
@primary-color: #1890ff; // 改成我们品牌的主色
@border-radius-base: 4px;
这样既享受了成熟组件库的稳定性,又保留了定制能力。用轮子,但要知道轮子怎么转——这是我的信条。
性能与兼容性:别让用户等得想卸载
上线前,我用 Lighthouse 测了一把,发现首屏加载居然 3.2 秒!罪魁祸首是:所有路由组件都同步加载。
解决方案:React.lazy + Suspense + 路由级代码分割。
// routes.tsx
const Dashboard = lazy(() => import('@/pages/Dashboard'));
const Settings = lazy(() => import('@/pages/Settings'));
function App() {
return (
<Router>
<Suspense fallback={<Spin size="large" />}>
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
</Router>
);
}
同时,在 index.html 里加上关键资源预加载:
<link rel="preload" href="/assets/logo.svg" as="image">
再配合 Vite 的 build.reportCompressedSize = true,监控打包体积。最终首屏降到 1.1 秒,Lighthouse 分数 92。
至于浏览器兼容性?我们内部系统,最低支持 Chrome 88,所以大胆用 ES2020+ 语法。但如果是 ToC 项目,就得考虑 Babel + core-js 了——还好这次不用。
最终成果 & 心得
| 指标 | 优化前 | 优化后 |
|---|---|---|
| 开发启动时间 | ~20s (Webpack) | <1s (Vite) |
| 首屏加载 | 3.2s | 1.1s |
| Bundle 体积 | 2.1MB | 1.4MB (gzip) |
| 代码规范违规 | 平均 5 处/PR | 0 |
项目如期上线,测试只报了两个低优先级 Bug(一个日期格式问题,一个边界空状态没处理),连产品经理都说“这次前端挺稳”。
最大的感悟是:
作为一个手写党,我曾经抗拒一切“自动化”、“脚手架”、“AI 辅助”。但这次实践让我明白:现代化工具不是偷懒,而是把精力从重复劳动中解放出来,专注业务逻辑和用户体验。现在我也开始试用 GitHub Copilot 了——当然,生成的代码我一定会逐行 review,毕竟,手写党的尊严不能丢 😎
如果你也在考虑跳槽或重构项目,不妨试试从零搭一次。过程痛苦,但收获远超预期。
最后,祝大家少加班,多上线,Bug 全是测试的锅。
作者:一个在 Mac 上敲代码、在 Windows 上测兼容性的云原生前端,正在看新机会。欢迎交流,但别问我要 Create React App 配置——我已经戒了。

评论 0