背景
公司的一个管理后台代码量过于庞大,同时所用的框架是umi3,由于umi3的拉垮表现,导致即使改一个字,热更新时间都需要5s-6s,构建时间更是长达10min,十分影响工作体验。在综合考虑之下,尝试升级umi版本,最终达到热更新时间提升到300ms(提升94%),打包构建速度提升到1min30s(提升85%)。
所用框架:umi4、antd5、antd pro
🌈写在前面
相信这是一篇全网最详细,踩坑经验最多的升级umi3经验分享。本篇文章会详细地介绍我从尝试升级到完成升级的各个阶段的具体做法,希望能帮助到大家顺利升级umi3到umi4。
🌟尝试1:直接按照官网的升级步骤进行升级(失败)
一开始我尝试直接按照官网的步骤去升级,但发现事情并没有这么简单,在简单的替换依赖后,会发现一系列疑难杂症,比如 运行报错,fatal - TypeError: api.addUmiExports is not a function(umi4使用了umi3的插件,不兼容)等等,弄了一天发现bug又多又偏门尝试无果后,果断放弃直接升级配置,尝试下一种升级方法。
🌟尝试2:创建umi4max模版,再逐步迁移(成功)
- 具体步骤:迁移package.json -> 迁移config.ts文件、routes配置,tsconfig配置等等 -> 迁移Layout页面 -> 迁移请求方法 ->迁移pages、components
1. 迁移package.json
- 按照官方文档,更换项目的启动方式。
- 注意自己项目的启动配置,如PORT,HOST配置等等。
- dependencies配置建议先不安装,可以在迁移组件的时候逐步安装。
2. 迁移config.ts文件、routes配置,tsconfig配置
- 这些配置实践中几乎可以无脑迁移,最好是对应pages下的页面一个一个迁移,方便debug,遇到bug再对应解决(如routes中有些path写法,在umi3中不会报错,但是在umi4中会报错)。
- 一定要按照官网的目录路径去写,否则可能会出现搜索不到文件位置的问题。
3. 迁移Layouts页面(踩坑经验适用于框架使用AntdPro的项目)
位于src/layouts文件中,umi中默认全局作用域,分左部导航栏以及右部内部区进行详解说明。
参考代码:
<ConfigProvider locale={zhCN}>
<ProLayout
route={routes[1]}
location={{ pathname: pathname }}
menuItemRender={(item: any, dom) => (
<div
className="w-full"
onClick={() => {
history.push(item.path);
setPathname(item.path);
}}
>
{dom}
</div>
)}
breadcrumbProps={{
itemRender(route: any) {
return (
<a
onClick={() => {
history.push(route.linkPath);
}}
>
{route.breadcrumbName}
</a>
);
},
}}
>
<PageContainer
content={<Outlet context={{ prop: setPathname }}></Outlet>}
></PageContainer>
</ProLayout>
</ConfigProvider>
内容区
- 右边内容区代码写在ProLayouts的子元素中(参考上方代码中的PageContainer),这里需要特别注意的是在umi4中已经不支持取props元素了,如果你使用的umi3中使用了props,注意一定要按照文档进行替换,在这里就是使用了Outlet来替代了children。
导航栏
- 点击跳转链接,menuItemRender中使用 history.push()进行跳转(基于你的路由配置)。
- 开启面包屑,这个自己踩了很多坑。要开启面包屑首先要配置location配置:location={{ pathname: pathname }},这个pathname怎么取呢,需要在OutLet中传参,在对应的涉及多级路由的子组件中去更新pathname(具体Outlet用法参考umi文档或者参考下面代码)。
//Layouts中
<PageContainer
content={<Outlet context={{ prop: setPathname }}></Outlet>}
></PageContainer>
//在子组件中:
const ChildrenComp = ()=>{
const { prop } = useOutletContext() as any;
//控制跳转编辑/新增页面的跳转方法
const handleAdd = ()=>{
history.push(xxx);//xxx为具体路由
prop(xxx);
}
return <>
</>
}
4. 迁移请求方法
这一步是耗费最多时间的一步,遇到了许许多多的bug,主要是因为umi4的底层请求方法改变了,使得许多配置需要更改。
参考代码:
//umi3写法
const request = extend({
errorHandler, // 默认错误处理
prefix: SERVICE_API_URL,
credentials: 'include',
})
request.interceptors.response.use(async response => {})
request.use(async (ctx: any, next) => {
await next()
})
//---------------------------------------------
//umi4写法
export const request: RequestConfig = {
// 统一的请求设定
baseURL: SERVICE_API_URL,
withCredentials: true,
//处理url params转义
paramsSerializer: function (params) {
return qs.stringify(params, { arrayFormat: 'brackets' });
},
// 请求拦截器
requestInterceptors: [
(ctx) => {
if (method == 'post' || method == 'put') {
ctx.data = handleDataRequest(data);
} else if (method == 'get') {
ctx.params = handleGetRequest(params);
}
return ctx;
},
],
// 响应拦截器
responseInterceptors: [
(response: any) => {
return response;
},
],
};
export const handleDataRequest = (opts: any) => {
const data = opts ? strictNullHandling(opts) : {};
(data['_t'] = new Date().getTime()),
(data['_r'] = randomString()),
(data['_s'] = decode(data));
return qs.stringify(data);
};
export const handleGetRequest = (opts: any) => {
const data = opts ? strictNullHandling(opts) : {};
(data['_t'] = new Date().getTime()),
(data['_r'] = randomString()),
(data['_s'] = decode(data));
return data;
};
- 第一步:自定义request迁移到src/app.ts,参考文档迁移对应配置。
遇到的坑
- 签名校验为空:在request中加上withCredentials: true。
- 在umi3中若使用了requestType=‘form’ ,在umi4中需要修改为 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',同时要将data使用qs.stringify(data)来转化(通过看umi-request文档可知)。
- 请求url中出现部分字符不转义:在request中添加paramsSerializer方法转义(参考上面代码)
5. 迁移pages,components
-
在完成配置和请求方法的迁移后,各个组件应该不会遇到太多的bug,值得注意的是要确认好新安装的依赖的用法是否与之前的用法是否一致(可能版本升级了,用法改变了)。
-
注意umi4中不再支持props写法(上面提过),所以如果在umi3中用到了props中location、history、match、routes,children等属性,要按照官方文档进行更改。
以上就是我从umi3升级到umi4踩过的坑和经验。如果大家公司的项目还在使用umi3且热更新速度和构建速度很慢的话,不妨尝试升级一下umi的版本,升级所带来的提升真的是巨大的,可以很好地提高工作效率。
希望这篇文章能给大家带来帮助。