亲手搭建 React 脚手架:从工程化思维到 Vite 配置实战
2026/9/19 20:36:56 网站建设 项目流程

我前阵子把手头一个维护了两年的 React 项目拆开重搭了一遍,说实话,平时天天用脚手架,总觉得它就是个npm init或者create-react-app一键生成的东西,但真到自己动手从零配一遍依赖、捋一遍目录、把构建链路、代码规范、接口代理这些环节全部走通之后,才意识到脚手架这玩意儿承载的工程化信息量远比想象中大得多。

这篇笔记不是教你背命令,而是想跟你分享我重新搭建 React 脚手架时踩过的坑、想清楚的逻辑,以及为什么我一直认为“亲手搭一次脚手架”是每个前端开发者值得做一遍的事。不管你是刚学 React 的初学者,还是写了两三年业务代码但一直没碰过构建配置的工程师,这篇文章应该都能帮你把“脚手架”三个字从黑盒变成白盒。

1. 为什么我建议你亲手搭一次 React 脚手架

1.1 脚手架到底在解决什么问题

很多人一听到“脚手架”就觉得是个初始化工具,跑完命令生成一堆文件就完事了。实际上脚手架解决的是三个层面的问题:第一,统一工程约定,比如目录长什么样、代码规范是什么、构建命令有哪些;第二,降低启动成本,让新成员 clone 下来就能跑,不需要凭感觉建目录、逐个查依赖;第三,沉淀最佳实践,把团队踩过的坑变成默认配置。

举个例子,一个没有任何脚手架约定的 React 项目,A 同事把接口请求放在src/api,B 同事放在src/services,C 同事直接写在组件里。代码 review 的时候为了目录结构吵来吵去,这种内耗比写代码本身还累。脚手架就是把这些琐碎约定固化下来,让团队把精力留给业务逻辑和性能优化。

所以你看,脚手架不是“生成一次就完事”的工具,它本质上是项目工程化的底座。底座不牢,后面加路由、加状态管理、加 CI/CD 都会处处别扭。这也是为什么有些项目明明业务代码不多,维护起来却特别难受——问题往往不在业务层,而在脚手架这一层。

1.2 主流方案选型:CRA、Vite、手工搭

React 社区最常见的脚手架方案有三条路线。第一条是 Create React App,简称 CRA,它是 React 官方出品的零配置方案,跑一条npx create-react-app my-app就能得到一个完整可运行的项目。优点是省心,缺点是黑盒,react-scripts把 webpack 配置全部封装起来了,你想改一个 alias、调一个 proxy 都要用eject或者各种 hack。

第二条是 Vite,最近两三年前端圈热度最高的构建工具。它基于原生 ES Module,开发服务器启动速度极快,冷启动基本在一秒以内,热更新也是毫秒级响应。Vite 同样提供了npm create vite@latest这样的脚手架命令,虽然它本身不绑定 React,但通过官方模板可以很方便地初始化 React + TypeScript 项目。

第三条是纯手工搭 webpack,也就是自己装webpackbabel-loaderhtml-webpack-plugin这一堆东西,把配置文件一行行写出来。这条路学习成本最高,但你对整个构建链路会有绝对的控制权。很多人觉得手工搭太折腾,我反而认为如果你想深入理解前端工程化,至少要走一遍这条路。

我自己这次实操用的组合是Vite + React + TypeScript,同时在关键节点上手工调整配置。原因后面详细说。

1.3 自己搭一次能学到的底层逻辑

亲手搭过一遍之后,你才会明白开发服务器为什么要配 proxy,tsconfig.json里的paths为什么能配路径别名,ESLint 和 Prettier 到底谁管代码质量谁管代码风格,husky是怎么在提交代码前拦住不规范操作的。这些知识在业务开发中不会要求你掌握,但一旦遇到构建报错、热更新失效、线上白屏这类问题,你能定位的速度会比别人快很多。

我见过不少同学,用脚手架三年,连npm run build之后产物放在dist这个基本事实都不太清楚。你可以不会手写 webpack 配置,但至少要知道脚手架帮你做了什么。把这一层知识补上之后,你再去看 React 生态里那些工具链相关的问题,会有一种豁然开朗的感觉。

2. 动手前先想清楚:整体设计与依赖选型

2.1 环境准备与版本选型

搭脚手架第一步不是敲命令,而是先确认本机环境。这里最容易翻车的就是 Node 版本。React 18 及其周边生态对 Node 版本有最低要求,Vite 4/5 要求 Node 14.18 或更高,Vite 5 甚至要求 18+。如果你用的是旧电脑,Node 还停留在 12,老老实实先升级。

我建议直接用 nvm 管理 Node 版本,不要手动去官网下一个安装包。nvm 的好处是可以随时切换版本,项目如果遇到老工程需要降级,也方便。装好之后跑node -v确认版本号,同时最好把包管理器也统一一下。现在社区主流是 pnpm,它的磁盘占用小、安装速度快,而且天然解决了幽灵依赖的问题。如果你之前一直用 npm,这次搭脚手架正好是个切换的好时机。

包管理器的选择会影响后面很多操作,比如npm install对应pnpm install,CI 里缓存策略也不一样。在我这次搭的脚手架上,我最终选了 pnpm,原因很直接:依赖安装速度快,node_modules目录结构干净。但注意,如果团队其他人不熟悉 pnpm,要提前打招呼,否则会有人用 npm 安装后产生一堆奇怪的 lock 文件冲突。

2.2 目录结构与模块边界

脚手架配好了目录结构,等于给项目划好了边界。我这次采用的目录结构是这样的:

src/ ├── api/ # 接口请求统一封装 ├── assets/ # 静态资源 ├── components/ # 通用组件 ├── hooks/ # 自定义 Hook ├── layouts/ # 布局组件 ├── pages/ # 页面级组件 ├── router/ # 路由配置 ├── store/ # 全局状态管理 ├── styles/ # 全局样式 ├── types/ # TypeScript 类型定义 └── utils/ # 工具函数

这个目录划分对应了业务开发中最常见的几个维度:网络层、展示层、状态层、工具层。每个目录的职责是清晰的,组件不能直接塞fetch请求,接口定义统一放api,全局类型统一放types。这样划分之后,新成员接手项目时能快速定位文件,代码 review 时也有了一个隐形的评审标准。

有一点要特别注意:目录结构不能套得太死。如果项目只有两三个页面,强行拆出layoutsstore反而显得臃肿。脚手架给你的是一套基线,你可以根据业务规模做减法,不必为了结构而结构。

2.3 核心依赖清单与选择理由

搭建一个可用的 React 脚手架,核心依赖大概分成几类。第一类是运行时依赖,包括reactreact-dom,如果项目需要路由,再加上react-router-dom。第二类是开发依赖,包括构建工具vite、类型检查typescript、代码规范相关的eslintprettier、提交钩子huskylint-staged

状态管理这块,如果项目不大,我建议先不上redux,用 React 自带的useStateuseReducer加上 Context 就能解决大部分需求。等确实遇到跨层级数据共享非常频繁的情况,再考虑引入zustandjotai,这两个库的 API 更现代,心智负担比 redux 小很多。

接口请求这块,axios还是目前的主流选择,它封装了拦截器、取消请求、错误处理这些能力,比原生fetch在管理上方便不少。你也可以选择react-query这类请求库,它们把服务端状态和客户端状态分开管理,能帮你省掉缓存、重试、加载态这些重复代码。不过新手阶段先从 axios 开始没问题,等业务复杂了再升级方案。

3. 核心实操:从零配置一套可用的 React 开发环境

3.1 初始化项目与基础配置

我用 Vite 初始化项目时执行的是这一条命令:

pnpm create vite my-react-app --template react-ts

这个命令会生成一个 React + TypeScript 的基础项目。初始化完成之后,src里默认有App.tsxmain.tsxvite-env.d.ts这几个文件。先别急着写业务代码,把默认模板里的App.cssindex.css里的演示样式清掉,然后看一眼main.tsx的内容。

如果你的目标是从零理解构建过程,我建议你创建完 Vite 项目之后,花点时间看一下生成的vite.config.tsindex.html,搞清楚 Vite 为什么能把.tsx文件直接跑起来。Vite 的核心机制是依赖预构建和原生 ES Module,开发环境下它并不会把所有代码打包成一个 bundle,而是按需把模块返回给浏览器。这也是它启动快的原因。

3.2 TypeScript 配置与路径别名

React 项目配 TypeScript 不是为了给自己找麻烦,而是为了在编译期把一类隐性 bug 拦截掉。tsconfig.json里有两个配置值得花心思:strictpaths

{ "compilerOptions": { "strict": true, "baseUrl": ".", "paths": { "@/*": ["src/*"] } } }

strict打开之后,TypeScript 会强制你处理nullundefined,一开始可能觉得烦,但习惯之后代码质量会明显提升。paths配置路径别名,这样你在组件里就可以写import Button from '@/components/Button',而不是一长串相对路径import Button from '../../../../components/Button'

配置完tsconfig.json之后,还要在vite.config.ts里同步配置别名,否则 Vite 在解析模块时识别不了@符号:

import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import path from 'path' export default defineConfig({ plugins: [react()], resolve: { alias: { '@': path.resolve(__dirname, 'src') } } })

这里有个容易踩的坑:__dirname在 ES Module 环境下不一定可用,如果用的是"type": "module",建议用fileURLToPath(new URL('./src', import.meta.url))来替代。我在第一次配置时就因为这个报过__dirname is not defined,排查了好一会儿。

3.3 开发服务器与接口代理配置

开发环境里最影响体验的一个配置是代理。前后端分离开发时,前端跑在localhost:5173,后端接口跑在localhost:8080,如果不配代理,前端直接请求/api/user会导致跨域报错。配代理的意思是,把开发服务器变成一个中转站,前端发的请求由它转发到目标服务器,浏览器就不会有跨域问题了。

server: { host: '0.0.0.0', port: 5173, open: true, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, '') } } }

这里rewrite的作用是把请求路径里的/api前缀再剥掉一层,这样后端接收到的就是干净的/user。如果你们后端接口本来就带/api前缀,那就不要配rewrite。这块一定要跟前端团队、后端团队提前对齐,不然就会出现前端写/api/user,后端要/api/user,代理已经帮你转发了但路径对不上,接口 404 的情况。

3.4 代码质量工具链:ESLint + Prettier + Husky

搭建脚手架时如果不把代码规范这块配好,后面再补会很被动。ESLint 负责找代码里的问题,比如未使用的变量、变量未定义、React hooks 依赖项错误;Prettier 负责统一代码风格,比如单引号还是双引号、行宽多少、缩进几格。两者分工不同,不能互相替代。

Vite 的 react-ts 模板默认会带一部分 ESLint 配置,但如果你从零搭,核心依赖是这些:

{ "eslint": "^8.0.0", "eslint-plugin-react-hooks": "^4.0.0", "@typescript-eslint/parser": "^6.0.0", "@typescript-eslint/eslint-plugin": "^6.0.0", "prettier": "^3.0.0" }

ESLint 配好后,再配 Husky。Husky 的作用是在 Git 钩子阶段拦截操作,最常见的场景是pre-commit钩子。我是在提交前执行lint-staged,这个工具会自动检查暂存区里的文件,只对你要提交的那几行代码做检查和格式化,不会满项目跑一遍导致提交巨慢。

// package.json 中配置 lint-staged { "lint-staged": { "*.{ts,tsx}": ["eslint --fix", "prettier --write"], "*.{css,scss}": ["prettier --write"] } }

配合lint-stagedpre-commit钩子命令是:

"prepare": "husky install"

然后在.husky/pre-commit文件里写:

#!/usr/bin/env sh . "$(dirname "$0")/_/husky.sh" npx lint-staged

配好之后的效果是,如果你提交的代码有格式问题、有 lint 错误,提交会被直接拦截。这看起来有点不近人情,但对团队代码库的健康度帮助极大。我刚上这套配置时,同事纷纷抱怨提交老是失败,磨合了两周之后就没人抱怨了,因为代码 review 时很少再为了缩进和引号浪费时间。

3.5 构建优化与产物分析

脚手架最终要能产出可部署的静态文件,所以构建配置也不能马虎。Vite 默认的构建配置已经比较合理,但有两个点值得手动调一调。

第一个是打包拆包。如果不做任何处理,第三方的包全部会打进一个巨大的vendor.js,首屏加载会很慢。可以用 Vite 的rollupOptions手动拆包,把reactreact-domreact-router-dom这些核心库单独拆出来:

build: { rollupOptions: { output: { manualChunks: { react: ['react', 'react-dom', 'react-router-dom'] } } } }

第二个是产物分析。装一个rollup-plugin-visualizer,构建完之后它会生成一个产物依赖图,你一眼就能看到是哪个包体积最大、哪个模块有重复引入。很多时候项目变慢了,不是你写的代码不行,而是某个第三方库体积大得离谱却只用了它一个 API。这时候就该考虑换库或者做按需加载了。

4. 搭建过程中最容易踩的坑:问题与排查实录

4.1 页面白屏:先分清是路由问题还是运行时问题

白屏是 React 项目里最常见也最让人头疼的问题,我在搭脚手架的时候故意复现过几次,就是为了梳理排查思路。白屏的成因基本可以分成三类。

第一类是入口挂载错误。检查main.tsx里的ReactDOM.createRoot(document.getElementById('root'))对应的root元素是否存在于index.html。有时候改了 HTML 模板之后这个 id 变了,或者元素被替换掉了,结果页面什么都没有。这种白屏最好排查,打开控制台看有没有报Target container is not a DOM element就够了。

第二类是路由导致的空内容。如果你的路由用了BrowserRouter,开发环境下没问题,但部署到 Nginx 之后,用户直接访问/user这种二级路径,刷新一下可能就 404 或者白屏了。这不是前端代码的问题,是服务器没有把请求都回退到index.html。解决方案是让后端把未知路径都 rewrite 到index.html,或者在不需要 history 路由语义时直接改用HashRouter

第三类是运行时 JS 错误。组件里抛了异常,React 会直接卸载整个组件树,页面就白了。而且报错信息往往是压缩后的,比如热搜里经常能看到Minified React error #130这种提示。遇到这种压缩错误,关键是打开浏览器控制台看完整堆栈,或者把NODE_ENV切到 development 模式跑一遍,报错信息会完整很多。我通常会建议项目在开发环境开启 source map,生产环境保留一份不发布的 map 文件用于线上问题排查,这也是大型团队常用的做法。

4.2 热更新失效或非常慢

热更新是开发体验的底线,如果每次改代码都整页刷新,开发效率会直线下降。我这次搭建时遇到过一次热更新完全不生效的情况,排查之后发现是文件命名大小写不一致导致的——组件文件名是UserProfile.tsx,但某个引用写成了Userprofile。Windows 或 macOS 默认的文件系统大小写不敏感,这种问题在本地不会暴露,但 Linux 环境下或者在热更新模块匹配时就会出错。

另外,React 组件热更新依赖@vitejs/plugin-react里的 react-refresh,如果你在文件里不是只导出组件,还导出了常量、工具函数,react-refresh 会因无法安全热替换而退化为整页刷新。这个问题可以通过 eslint 插件react-refresh/only-export-components来拦截,保证一个文件只导出组件,这也是维护良好热更新体验的规范。

热更新变慢还有一个常见原因是项目过大,依赖太多。Vite 虽然开发启动快,但如果你把所有依赖都放在一个 loader 链里不做 exclude,转换耗时也会上升。常规做法是在optimizeDeps.exclude里排除一些不需要预构建的库,或者按需引入而不是全量引入。

4.3 构建时内存溢出

构建本身没问题,但一到 CI 或者本地npm run build就报JavaScript heap out of memory,这个问题我在配置比较大的项目时遇到过。原因是 Node 默认的堆内存上限大概是 1.5GB 到 2GB,当你的项目依赖非常多、构建产物很大时,默认内存不够用。

解法很简单,给 Node 进程增加堆内存:

"build": "node --max-old-space-size=4096 node_modules/vite/bin/vite.js build"

注意这里没有直接用vite build,因为需要先调整 Node 内存再执行构建脚本。如果你用的是 webpack,方式类似,只是入口文件换成node_modules/webpack/bin/webpack.js。配置完之后通常能解决大多数内存溢出问题。当然,如果项目真的庞大到 4GB 都不够,那就该考虑构建缓存、拆包粒度是不是不够合理了。

4.4 依赖版本冲突与引擎警告

搭建脚手架时最容易出问题的反而不是配置本身,而是依赖版本之间的兼容性。react@18ReactDOM.render已经被标记为弃用,改用createRoot创建渲染入口;@vitejs/plugin-react会要求特定版本的 Vite,不能随意升降级;react-router-dom6 和 5 的 API 差异也非常大,Switch改成了Routes,新手很容易从网上抄一段老代码进来直接爆红。

我的建议是,脚手架里的核心依赖版本尽量保持一个中高版本,不要用太新的 beta 版,也不要为了兼容旧代码锁在太老的版本上。安装依赖时留意 npm/pnpm 输出的 peerDependencies 警告,它其实是在提醒你某个包的同伴依赖没有满足,忽略掉往往会在运行时报奇怪的错。

4.5 常见问题速查表

我把搭建过程中的典型问题整理成了一张表,方便你快速定位。

现象可能原因排查方向
页面白屏root 元素缺失、路由刷新、JS 运行时错误看控制台完整报错,确认路由模式与部署环境
热更新失效文件大小写不一致、文件同时导出组件和常量统一小写命名,按 eslint 规则拆分文件
构建内存溢出Node 堆上限不足、依赖过于庞大调整--max-old-space-size,优化拆包
模块路径找不到alias 只在 tsconfig 配了没在 vite 配检查两处配置是否同步
刷新 404BrowserRouter 部署未回退 index.html后端配置 history 路由回退
Minified React error生产环境压缩报错开启 source map,切换环境复现

5. 脚手架落地后的扩展思路:从工程化到选型边界

5.1 基于脚手架沉淀团队规范

脚手架搭好之后,不要急着散伙,真正让这套配置发挥价值的是后续的迭代和沉淀。我见过一个团队把脚手架仓库单独维护,组里每出现一个通用问题,就先生产解决方案,再沉淀进脚手架模板里。比如他们抽了统一的错误上报组件、统一的业务国际化方案、自动化路由生成脚本,这些能力都集成在脚手架里,新项目 clone 之后天然具备。

这种思路就是公司内部的“前端基础设施”,比每个项目单独装一堆依赖、各自配各自的规则,要有价值得多。你可以从最基础的做起:在 README 里写清楚命令、目录规范、发版流程,再把常用业务组件放到脚手架自带的components里。时间一长,脚手架就成了团队真正的资产。

5.2 React 与 Vue 在脚手架选型上的差异

很多人在选型时会纠结 React 和 Vue,我在搭建脚手架的实践中对两者的差异也有一些体会。React 的脚手架生态相对松散,官方没有一个强制绑定的一体化框架,你可以用 Vite、webpack,也可以直接用 Next.js;而 Vue 官方有 Vite 驱动的create-vue,脚手架与官方工具链绑定得更深,用起来更顺滑。

另一个差别体现在代码组织和状态更新的心智模型上。React 的函数组件里每次渲染都会重新执行整个组件函数,很多人初学时会疑惑为什么每次都要返回一个新的render结果,这本质上是因为 React 的渲染模型是“渲染函数每次渲染都要执行”,配合虚拟 DOM 和 Fiber 的调度机制,React 才能在更新时精准找到需要变更的节点。而 Vue 的模板编译在运行时帮开发者做了很多优化,组件模板中的静态节点会被自动标记,更新粒度更细。在实际工程里,这两种模型没有绝对的优劣,选择更多取决于团队熟悉度和具体项目类型。

5.3 什么时候选 Next.js,什么时候继续用 Vite + React

脚手架到后面一定绕不开一个问题:我到底要不要直接上 Next.js?如果你做的是以 SEO 为主的内容站、落地页、博客这类偏展示型项目,Next.js 的 SSR/SSG 能力会非常有价值,因为它直接输出 HTML,首屏渲染和搜索引擎抓取都好得多。Next.js 自带路由、图像优化、服务端函数等能力,脚手架给你提供了更完整框架,代价是它对项目的约定比 Vite + React 更强,你需要在它的约定下开发。

如果你做的是后台管理系统、中后台业务工具这类对 SEO 要求不高、交互相对复杂的应用,Vite + React 的组合会更轻快。它的构建链路简单,开发调试直观,也没有 SSR 带来的部署复杂度。在我实际经验里,中后台项目占了国内 React 使用的大头,这类项目真的不需要 SSR,把首屏资源和路由拆好,体验已经足够。

所以答案不是二选一,而是先看应用对首屏渲染和 SEO 的需求,再看团队对框架约定是否认可。脚手架只是起点,它应该服务于业务形态,而不是反过来因为一套配置很炫就强行套用。

我在实际搭建过程中最大的体会是:脚手架不是一次性工作的终点,它更像是你和项目之间的一份长期契约。你可以在后续每次遇到构建问题、性能瓶颈、规范冲突时回来修改这份契约,把心得沉淀回配置。以后再开新项目,就不是从零开始,而是从你已经熟悉的最佳实践出发,这才是自己动手搭脚手架最深层的回报。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询