ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Umi 框架初探:React 前端工程化中的约定式开发实践

Umi 框架初探:React 前端工程化中的约定式开发实践 如果你和我一样常年泡在 React 项目里应该对下面这个场景特别熟悉项目用脚手架拉起来之后先装路由、再配代理、补 Mock、调打包配置、加状态管理……业务一行代码没写一上午已经没了。最近我把 Umi 从头到尾认真过了一遍最大的感受就是它把这些重复劳动全部收编成“约定 插件”你要做的只是填业务。这篇初探记录我会把选型理由、项目初始化、路由体系、联调配置还有我实际踩过的坑一次说清楚。适合刚接触 Umi 的前端同学也适合在多套 React 项目之间反复搬运配置的团队参考。1. 为什么要用 Umi先搞清楚它在解决什么问题1.1 从“能用”到“好用”脚手架只是起点React 生态里能帮你把工程搭起来的工具其实不少但它们大多只解决“编译能跑”这一个点。跑起来之后你会发现自己面前还有一排问题排队等着路由用哪个版本、请求库怎么封装、代理怎么配、Mock 要不要上、CSS 怎么处理、代码分割怎么做、环境变量怎么区分……这些问题单个看都不难但组合在一起就是一笔不小的维护成本。我见过很多项目光package.json里的依赖就有上百个其中一半是“为了把框架拼起来”而装的。更头疼的是这些依赖的版本和配置是强耦合的升级一个就可能连锁炸一片。Umi 做的事情是把这些“组装工作”从你手里拿走让框架本身替你做出默认选择。它内置了路由、打包优化、代理、Mock、环境变量处理、SSR/预渲染等一堆能力你只需要聚焦业务代码。用一句不太准确但很好懂的话来说普通脚手架给你一个毛坯房Umi 给你的是已经走好水电的屋子你直接往里摆家具就行。1.2 插件化架构把“框架”变成“积木”Umi 最核心的设计不是它自带的某个功能而是它的插件机制。整个框架更像一个可插拔的底座路由、代码分割、快速刷新、状态管理、权限、布局这些能力本质上都是一个个插件。这个设计的价值在于“按需使用”。你不用为了一个权限功能被迫引入一堆无关依赖反过来如果你需要某种团队内部特有的能力也可以自己写插件注入到框架的编译流程里。项目在这个机制下像是一个集装箱每块功能都是标准卡槽需要哪块就插哪块。实际使用中你会发现Umi 的插件不是“能用就行”的程度而是可以深度定制编译流程的。比如你想在构建时自动注入一段脚本、想统一修改产物文件名、想对某个路由做特殊处理都可以通过插件 API 去介入。对个人项目这可能是锦上添花但对一个长期维护的团队项目来说这是能不能形成“自己的工程体系”的分水岭。1.3 哪些团队最适合接 Umi说点掏心窝的话不是所有项目都适合上 Umi。我会优先推荐给这几种场景项目多、团队小团队同时维护好几个中后台系统如果每个项目的构建配置都是复制粘贴出来的后期维护就是噩梦。Umi 把公共能力收敛到框架里团队统一版本、统一约定成员在不同项目间流动时几乎没有学习成本。前后端分离但联调频繁代理和 Mock 是 Umi 的开箱能力后端接口没写好、环境不稳定的时候前端可以完全独立跑通自己的流程。想快速出原型或做内部工具约定式路由意味着“新建一个文件就是一个页面”这种爽感在你赶工的时候会特别明显。对框架有洁癖但又不想要黑盒插件机制给了一条出口团队可以把公共规范沉淀成内部插件而不是散落在各个项目里的“复制粘贴文档”。反过来说如果你做的项目非常小只有一个页面、一次交互那没必要为了 Umi 多一层心智负担如果你追求的完全是零依赖、手动掌控一切的极简主义那 Umi 这种“全家桶”式的集成反而会让你难受。工具没有绝对的好只有匹配不匹配。2. 5 分钟快速初始化 Umi 项目环境、命令与目录拆解2.1 环境准备Node 版本与包管理器Umi 4 对 Node 版本有要求一般建议使用 16 及以上的版本我实际测试时在 18 和 20 的 LTS 上都很稳。如果本机版本太旧第一步就会死在依赖安装上。包管理器方面官方场景里 npm、yarn、pnpm 都支持但我个人更推荐 pnpm。原因很实在Umi 的依赖树不小pnpm 的硬链接机制能让安装速度快不少磁盘占用也低。如果是团队协作一定要把包管理器锁死在项目里最直接的办法就是在根目录放packageManager字段或者.npmrc避免有人用 npm 装、有人用 yarn 装最后 lock 文件互相打架。2.2 脚手架命令从空目录到跑起来初始化 Umi 项目非常直接我习惯在一个空目录里执行# 确认 Node 版本 node -v # 用 pnpm 初始化 Umi 项目 pnpm dlx create-umilatest # 或者用 npm npx create-umilatest执行之后会出现交互式提示让你选择应用类型。初次上手建议选“简单应用Simple App”它生成的项目结构最精简适合用来理解 Umi 的核心机制。等你熟悉了再去尝试那些带更多内置能力的模板。项目生成后按提示安装依赖然后启动pnpm install pnpm dev不出意外的话编译完成后终端会提示访问http://localhost:8000。第一次看到页面秒开的时候我已经对这个架子产生好感了——它没有把我丢进一堆配置里而是先给我一个能跑的最小闭环。2.3 目录结构几个必须认识的约定Umi 是“约定优于配置”的典型代表所以目录结构本身就在表达规则。初始化出来的精简项目大致长这样├── src/ │ ├── pages/ # 约定式路由的页面目录 │ │ ├── index.tsx # 对应路径 / │ │ └── users/[id].tsx # 动态路由对应 /users/:id │ ├── layouts/ # 全局布局可选 │ └── app.ts(x) # 运行时扩展登录态、请求配置等 ├── config/ │ └── config.ts # 项目配置文件 ├── mock/ # 本地 Mock 数据目录 ├── .env.local # 本地环境变量 └── package.json这里有两个新手特别容易踩的点我提前帮你排一下雷。第一个是配置文件的位置Umi 允许.umirc.ts和config/config.ts二选一但不要两个都放。我个人建议用config/config.ts因为项目复杂后配置可以继续拆分成多个模块放进config目录而.umirc.ts只适合极简场景。第二个是pages目录名的敏感度Umi 对页面文件的大小写、命名格式都有约定如果新增文件后发现路由没生效先检查文件名和路径大概率是大小写不一致。3. 路由体系详解约定式路由与配置式路由的组合打法3.1 约定式路由新建文件就是新增页面Umi 最让人上头的设计之一就是约定式路由。核心规则很简单src/pages目录下的文件路径就是页面的路由地址。// src/pages/index.tsx export default function HomePage() { return div这里是首页/div; }这个文件对应的是/。如果再建一个src/pages/users.tsx那它就自动映射到/users完全不需要去某个配置文件里声明路由。文件路径和路由的对应关系看这张表就够了文件位置对应路由说明src/pages/index.tsx/首页src/pages/users.tsx/users普通页面src/pages/users/[id].tsx/users/:id动态路由src/pages/404.tsx自动兜底未匹配路径动态路由是实际项目里很高频的需求比如用户详情页/users/123。在 Umi 4 里写法是把动态段用方括号包起来文件名叫[id].tsx然后页面里通过useParams取参数import { useParams } from umi; export default function UserDetail() { const { id } useParams(); return div当前用户 ID{id}/div; }这种“文件即路由”的模式对团队协作特别友好。后端接口文档里写一个“新增用户详情页面”你只需要往pages里丢一个文件路由、懒加载、代码分割全自动搞定。项目早期页面增长快的时候这个优势会被放大得很明显。3.2 配置式路由复杂场景下的一页纸方案约定式路由很好用但遇到权限控制、嵌套布局、需要动态生成路由的场景它就显得不够灵活了。这时候可以切换成配置式路由把所有路由集中写在config/config.ts里import { defineConfig } from umi; export default defineConfig({ routes: [ { path: /, component: /layouts/index }, { path: /login, component: ./login }, { path: /users, component: ./users/index, routes: [ { path: /users/list, component: ./users/list }, { path: /users/:id, component: ./users/[id] }, ], }, { path: *, component: ./404 }, ], });这里需要注意几个点。一是配置式路由下component的路径默认相对src/pages也可以用/这种别名指向其他目录比如上面的/layouts/index。二是路径一定要写完整子路由的path不要只写相对片段否则很容易出现“页面找得到但 URL 对不上”的诡异问题。三是约定式路由和配置式路由不要混用一旦你声明了routes字段约定式路由就会关闭两边同时存在只会让你怀疑人生。什么时候该用配置式路由我的经验是当项目开始出现“这个页面要登录才能看”“这几个页面共用一套布局”“路由需要根据用户角色动态生成”这一类需求时就该切过去了。它是比约定式更重、但掌控力更强的方案。3.3 布局、权限与动态路由的实战结合先看布局。Umi 支持全局布局文件src/layouts/index.tsx它会在页面外层自动包裹一层结构import { Outlet } from umi; export default function GlobalLayout() { return ( div header style{{ padding: 16, borderBottom: 1px solid #eee }} 这里是公共头部 /header main style{{ padding: 16 }} Outlet / /main /div ); }注意子页面通过Outlet /渲染这是路由嵌套的标准玩法。在配置式路由里直接在顶层路由的component指向这个 Layout 即可。权限是另一个高频诉求。Umi 提供wrappers机制可以给某个路由套一层“包装组件”在渲染页面之前做拦截{ path: /admin, component: ./admin/index, wrappers: [/wrappers/auth], }然后在src/wrappers/auth.tsx里写校验逻辑import { Navigate } from umi; export default function AuthWrapper({ children }) { const isLogin localStorage.getItem(token); if (!isLogin) { return Navigate to/login replace /; } return children; }这套组合拳打下来布局和权限就从“散落在每个页面里的重复代码”变成了“路由层面的统一约束”。我在实际项目里最喜欢的是这种收拢感你不需要在每个页面里复制一段判断登录的逻辑系统级的规则就应该在系统级处理。4. 本地开发三件套Mock、代理与多环境配置4.1 代理配置前后端联调不再改代码前端开发最烦的一件事就是接口联调时跨域。Umi 的 dev server 内置了代理能力在配置文件里加一段proxy即可export default defineConfig({ proxy: { /api: { target: http://127.0.0.1:8080, changeOrigin: true, pathRewrite: { ^/api: }, }, }, });解释一下这三个关键配置的含义。target是后端真实地址changeOrigin表示把请求头里的 Host 改成目标地址很多后端会校验 Host不设置就可能被拒pathRewrite是把浏览器端请求的路径做一次改写——比如浏览器请求/api/user经过代理后会变成http://127.0.0.1:8080/user也就是把/api这个前缀去掉。这里有个新手容易迷糊的点代理只作用于本地 dev server跟你部署到服务器之后毫无关系。生产环境通常由网关或反向代理来转发/api所以不要在代码里写死任何环境相关地址统一用相对路径/api/xxx发起请求。4.2 Mock 数据后端没写好也能先跑页面Umi 的 Mock 能力是我在所有前端框架里用下来最顺手的。做法很简单在根目录新建mock文件夹放一个 TS 文件// mock/api.ts export default { GET /api/user: { name: 张三, id: 1 }, POST /api/login: (req, res) { const { username } req.body; if (username admin) { res.json({ token: mock-token }); } else { res.status(401).json({ message: 用户名错误 }); } }, };字符串形式的写法适合直接返回静态数据函数形式的写法可以处理请求体、返回不同状态码。我在联调阶段最常用的姿势是先用 Mock 把页面交互完整跑通后端接口就绪后一键关掉 Mock 切换到真实接口业务代码几乎不用改。这里要提醒一点Mock 文件修改后不同版本 Umi 的热更新行为不完全一样如果你发现改了文件但接口返回没变不用怀疑自己写错了直接手动重启一下pnpm dev基本都能解决。另外Mock 目录默认只在开发环境下生效这正好符合我们的使用场景。4.3 环境变量与 UMI_ENV一套代码多套环境多环境是每个正经项目的必修课。Umi 约定根目录下的.env、.env.local、.env.{UMI_ENV}会被自动读取。比如# .env UMI_APP_TITLE本地环境 API_BASE_URL/api # .env.prod UMI_APP_TITLE生产环境正常情况下process.env.UMI_APP_TITLE可以在代码里直接使用。注意一个细节不是所有环境变量都会暴露到浏览器端只有以UMI_APP_开头的变量会被安全注入其他变量仅存在于 Node 侧。这个设计是为了防止你不小心把密钥之类的东西打进前端包。那UMI_ENV怎么用它是 Umi 自己读取的环境变量标识用来决定加载哪个.env文件。举个例子# 开发时默认加载 .env pnpm dev # 构建生产包时加载 .env.prod UMI_ENVprod pnpm buildWindows 下设置环境变量不如 Linux 那么直接我一般用cross-env来抹平差异。这一整套组合下来一套代码可以在本地、测试、生产三套环境间无缝切换部署脚本里只需要改UMI_ENV的值。5. 插件生态与常见问题排查我的踩坑记录5.1 常用官方插件先看看有没有现成轮子Umi 生态里相当一部分能力是通过插件提供的。初始化项目时交互式命令行就会让你勾选需要的功能比如组件库接入、全局布局、状态管理、请求封装、权限控制等。我的建议是不要嫌预置能力多但也不要在小项目里全选。这里分享一个选型原则**优先使用官方维护的插件其次是社区高星插件最后才考虑自己造轮子。**原因很简单Umi 的插件和框架版本耦合比较紧官方插件会跟随大版本及时适配社区插件则可能出现“框架升级了、插件没人维护了”的尴尬情况。我自己就遇到过依赖的社区插件在 Umi 4 下不兼容最后不得不替换方案的情况。5.2 高频报错与解决对照表整理一份我实测中最高频的问题对照表按“现象 → 可能原因 → 处理方式”的格式来希望能帮你少走点弯路现象可能原因处理方式启动时报 Node 版本不支持本地 Node 过旧或过新切换到 LTS 版本18/20删除 node_modules 重装Mock 接口返回 404 或旧数据文件不在 mock 目录 / 没重启确认目录名和导出格式手动重启 dev改了配置不生效同时存在 .umirc.ts 和 config/config.ts删掉 .umirc.ts只保留一份配置代理请求 404pathRewrite 没配或后端路径不一致先用浏览器直连后端确认地址再检查 rewrite 规则新增页面路由不出现文件名大小写或路由缓存问题检查 pages 路径刷新浏览器必要时重启 dev这里面最容易被忽略的就是“同时存在两份配置文件”的情况。Umi 会优先读取某一个但你可能会以为改的是生效的那份结果改了半小时发现一直在改一个“假配置”。遇到配置类问题第一件事永远是确认你改的文件确实被项目加载了。5.3 最后分享几条避坑心得第一先跑通官方文档的快速开始别一上来就照抄网上零散文章。Umi 2、3、4 之间的差异很大尤其是动态路由写法和部分配置项名称都变过很多网上代码放到 Umi 4 里就是跑不起来的。以你当前所用大版本的官方文档为准这是最稳的路径。第二项目打算长期维护的话尽早把config/config.ts里的公共配置抽成独立函数或模块。我实际用下来的体会是越是项目变大集中式配置越重要。散落在各个文件里的“临时配置”是最难维护的因为它们没有规律可循。第三遇到“改了配置但表现完全没变”的诡异问题先别急着怀疑框架。多数情况下是缓存或者残留的临时文件在作怪。把项目根目录下的临时产物目录删掉再重新pnpm dev一次八成能解决。如果还不行跑一下umi setup重新生成项目依赖文件也能救回来很多莫名其妙的问题。最后再分享一个我自己的直观感受框架好不好用不在别人怎么说而在于你写业务时是不是真的少操了那份心。第一次跑通 Umi 的 dev、看着约定好的文件变成页面自动刷新的那一下我确实松了口气。如果你在 Umi 里也折腾出过奇怪的坑或者发现了值得分享的玩法欢迎按着这套思路自己动手搭一个项目试试。纸上得来终觉浅配置这种东西永远是自己跑通一遍才记得住。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表