umi3 → umi4 升级:踩坑与解决方案

前言

当前项目使用 umi3,为了提升开发效率和体验,决定将项目从 umi3 升级到 umi4。本文记录升级过程中遇到的问题和解决方案。

一、umi4 的核心变化

在开始踩坑之前,先给大家介绍一下升级 umi4 后两个变化最大的部分:

1.1 路由系统:React Router v5 → v6

这是影响最大的变化,导致了大量 API 需要迁移,具体改动点在 umi 的升级文档里面有列出,这里就不赘述了。

ts 复制代码
// umi3 (React Router v5)
import { useHistory } from 'umi';
const history = useHistory();
history.push('/path');

// umi4 (React Router v6)
import { useNavigate } from 'umi';
const navigate = useNavigate();
navigate('/path');

1.2 Layout 插件机制

umi4 改成了自动查找 layout,子路由逻辑也做了修改,直接替换就行。

维度 umi3 umi4
布局发现 需要在路由中指定component 自动查找src/layouts/index.tsx
子路由渲染 props.children Outlet 标签

二、踩坑实录

接下来我会逐个介绍这次升级遇到的问题和解决方案,可能各个项目的情况有所不同,不一定遇到相同的问题,可以直接在目录中找到遇到的问题。

2.1 SSO 401 跳转问题

本地启动后跳转 sso 登录页面,点击登录后还是会调到sso登录页面,后来去排查接口请求,发现是页面上的请求都返回401,在确认接口没问题后,对比了一下请求头,发现请求头缺少 accessToken,所以问题是请求没带上 accessToken。

原因:umi3 会自动在请求中添加 accessToken,umi4 不再自动注入。

解决方案:在请求拦截器中手动添加 accessToken。

2.2 导航嵌套问题

页面出现两层导航栏互相嵌套。

原因

  • umi4 会自动查找并应用 src/layouts/index.tsx
  • umi4 会通过 渲染子路由,如果路由配置中又指定了 component: '@/layouts',会导致布局渲染两次。

解决方案

去掉之前在 routes 文件里面定义的 layout。

ts 复制代码
{
  path: '/',
  // component: '@/layouts',  // 注释掉这行
  routes: [...]
}

2.3 SVG 图标不显示

部分图标没有显示出来,排查发现没显示出来的都是 iconfont 的 SVG 图标。接着检查资源里面没有 iconfont.js。

原因: 在代码中查看 iconfont 引用的地方 document.ejs,发现使用了 <%= context.config.publicPath %>iconfont.js引入文件,在 umi4 中,context.config.publicPath 这个模板变量不再可用。

解决方案 :在 app.tsx 中运行时动态加载:

ts 复制代码
if (typeof window !== 'undefined') {
  const script = document.createElement('script');
  script.src = '/iconfont.js';
  script.async = true;
  document.head.appendChild(script);
}

2.4 KeepAlive + useModel 白屏

当子路由替换成 Outlet 标签后,页面出现白屏,控制台出现报错:

排查过程

  1. 根据报错信息,定位到报错发生在 useModel 调用处
  2. 通过搜索 GitHub Issue 找到相关讨论:umi#10334
  3. 确认是 KeepAlive 的缓存机制导致 React Context 传递链断裂

原因: 当 useModel 在 KeepAlive 包裹的组件内部调用时,可能无法正确访问到 umi 提供的全局 Context,从而导致 dispatcher 为 null。

解决方案:将 useModel 调用移到 KeepAlive 外部,通过 props 向子组件传递数据:

2.5 React Router v6 带来的 API 变更

umi4 升级了 React Router 到 v6,导致一系列 API 变化,具体可以参考umi的官方升级文档。

useHistory → useNavigate

ts 复制代码
// umi3
import { useHistory } from 'umi';
const history = useHistory();
history.push('/path');
history.replace('/path');

// umi4
import { useNavigate } from 'umi';
const navigate = useNavigate();
navigate('/path');
navigate('/path', { replace: true });

location.query 不再支持

ts 复制代码
// umi3
const { id } = location.query;

// umi4
import { parse } from 'query-string';
const query = parse(location.search);

props 中的参数不能直接获取

umi4 中 props 默认为空对象,locationhistorymatch 等都不能直接从 props 获取,需要使用对应的 hooks。

ts 复制代码
// umi3
export default function Page(props) {
  const { location, history, match } = props;
  props.location;
  props.history.push('list');
  props.match
}

// umi4
import { useLocation, history, useMatch } from 'umi';
export default function Page() {
  const location = useLocation();
  history.push('list');
  const match = useMatch({ path: 'list/:id' });
}

2.6 ProLayout headerRender 不生效

现象 :ProLayout 的 headerRender 属性设置后,自定义 header 不显示。

解决方案 :不使用 headerRender,改为将 header 组件放在 ProLayout 的 children 中:

2.7 Cursor 编辑器卡顿

现象:升级后使用 Cursor 打开项目非常卡。

原因 :umi4 本地启动的时候会生成 .umi 的临时文件夹,内容比较多,Cursor 索引时会消耗大量资源。同时 VSCode 也会监听 .umi 的文件导致卡顿。

解决方案 :在项目根目录创建 .cursorignore 文件让 Cursor 忽略:

txt 复制代码
.umi
.umi-production

同时在 .vscode 的 settings.json 中也要添加对.umi的忽略:

2.8 无法从 'umi' 导入的问题

现象:之前从 'umi' 导入的某些函数报错找不到。

原因 :项目中自定义的 model 初始化函数(如 getIndicatorInitStategetDatasetInitState)在 umi3 中可能通过插件挂载到 umi 命名空间,umi4 不再支持。

解决方案

ts 复制代码
// Before: umi3
import { getIndicatorInitState, getDatasetInitState } from 'umi';

// After: umi4
import { getIndicatorInitState } from '@/models/indicator';
import { getDatasetInitState } from '@/models/dataset';

三、升级 Checklist

最后贴一下我本次 umi 升级的主要流程

  1. 准备工作
  • 项目备份
  • 清理缓存
  1. 依赖升级
  • 更新 package.json 依赖
  • 更新脚本命令
  1. 配置文件调整
  • 更新 config.ts 配置,创建 .env 文件
  • 更新 routers 和 layouts 文件
  1. 报错处理
  • 本地启动,检查命令行报错
  • 检查控制台报错
  • 启动后,检查样式问题
  1. 构建和测试
  • node 版本升级
  • 构建测试环境

四、总结

本次升级除了上述问题还遇到了其他很多问题,篇幅有限简单问题就没提了,升级中很多的步骤都是依靠 Cursor 来协助完成的,比如依赖的变更,配置文件的迁移,Cursor 会比我更加的了解 umi4 的文档一些,出现的很多问题也会很好的解决,但是也会有一些局限性,比如 KeepAlive 导致白屏的问题,反复提问都无法正确解决,最终是搜到了相同的报错,提供了 Issue 给 Cursor 才能正确解决问题。总的来说要善用 Cursor 提升效率还是比较明显的。

参考资料

相关推荐
徐小夕2 小时前
知识库创业复盘:从闭源到开源,这3个教训价值百万
前端·javascript·github
xhxxx2 小时前
函数执行完就销毁?那闭包里的变量凭什么活下来!—— 深入 JS 内存模型
前端·javascript·ecmascript 6
StarkCoder2 小时前
求求你试试 DiffableDataSource!别再手算 indexPath 了(否则迟早崩)
前端
fxshy2 小时前
Cursor 前端Global Cursor Rules
前端·cursor
红彤彤2 小时前
前端接入sse(EventSource)(@fortaine/fetch-event-source)
前端
十一.3663 小时前
103-105 添加删除记录
前端·javascript·html
用户47949283569153 小时前
面试官:DNS 解析过程你能说清吗?DNS 解析全流程深度剖析
前端·后端·面试
涔溪3 小时前
微前端中History模式的路由拦截和传统前端路由拦截有什么区别?
前端·vue.js