上一篇文章是从umi2升级到umi3,这一篇记录一下umi3升级到umi4的过程以及碰见的问题。
官方链接查看
升级步骤
- 依赖处理
- 启动命令修改
- 非官方插件升级
- 配置文件修改
- 代码层修改
依赖处理
项目的 package.json 需要升级 Umi,并替换掉对应的 Umi 插件。
如果 umi@3 中是使用 umi + @umijs/preset-react 的组合进行开发的,那可以直接使用新版的 max 直接升级。
{
"devDependencies": {
+ "@umijs/max": "^4.0.0",
- "umi": "^3.0.0",
- "@umijs/preset-react": "^1.2.2"
}
}
删除 node_module,执行下 npm install 重装依赖。
启动命令
如果使用了 @umijs/max 可以使用 max 命令来替换 umi,max dev,max build 等。
umi@4 将一些项目前置操作放到了 setup 命令中,如 umi@3 中的 umi g tmp 等命令,需要使用 umi setup 替换
package.json
{
"scripts": {
- "build": "umi build",
+ "build": "max build",
- "start": "umi dev",
+ "start": "max dev",
}
}
非官方插件升级
项目迁移时可先关闭对相应插件包的引用,如临时注释配置中的 plugins,移除 package.json 中以 umi-plugin-,@umijs/plugin- 和 @umijs/preset- 开头的所有依赖。
配置层迁移
max 提供的的配置项如下 config/config.ts :
需要注意的是,之前的一些插件约定开启的规则,在
umi@4中几乎都要通过显式的配置开启,因为希望在umi@4中有更少的“黑盒”。
import { defineConfig, utils } from 'umi';
export default defineConfig({
model: {},
antd: {},
request: {},
initialState: {},
mock: {
include: ['src/pages/**/_mock.ts'],
},
dva: {},
layout: {
// https://umijs.org/docs/max/layout-menu#构建时配置
title: 'UmiJS',
locale: true,
},
// https://umijs.org/zh-CN/plugins/plugin-locale
locale: {
// default zh-CN
default: 'zh-CN',
antd: true,
// default true, when it is true, will use `navigator.language` overwrite default
baseNavigator: true,
},
});
存在差异的配置项如下 config/config.ts :
import { defineConfig, utils } from 'umi';
export default defineConfig({
- fastRefresh: {},
+ fastRefresh: true,
dva: {
// 不再支持 hmr 这个参数
- hmr: true,
},
// 默认 webpack5
- webpack5: {},
})
其他涉及到的修改
- 移除了dynamicImport,umi4 默认按页拆包,该行为近似等同于以前的
dynamicImport,通过src/loading.tsx定义加载动画。 - ignoreMomentLocale内置,默认值为true
- 分包配置,按照一定的优化策略进行自动分包这里也可以自己配置分包规则。
codeSplitting: {
jsStrategy: "granularChunks",
},
- moment2dayjs,只要注册了moment2dayjs, moment就会被dayjs代替。默认情况下注册可以适应and的预设。当用户同时配置预设和插件时,会合并处理。
- analyze,配置项同
webpack-bundle-analyzer
代码层修改
Umi 4 中将 react-router@5 升级到 react-router@6,所以路由相关的一些 api 存在着使用上的差异。
props 默认为空对象,以下属性都不能直接从 props 中取出
children
import { Outlet } from 'umi';
<Outlet />;
主要在全局 layout 中需要修改
如 layouts/index.tsx:
import React from 'react';
+ import { Outlet } from 'umi';
export default function Layout(props) {
return (
<div>
- { props.children }
+ <Outlet />
</div>
);
}
使用了 React.cloneElement 方式渲染的路由组件改造,示例
import React from 'react';
+ import { Outlet } from 'umi';
export default function RouteComponent(props) {
return (
<div>
- { React.cloneElement(props.children, { someProp: 'p1' }) }
+ <Outlet context={{ someProp: 'p1' }} />
</div>
);
}
组件改成从 useOutletContext 取值
import React from 'react';
+ import { useOutletContext } from 'umi';
- export function Comp(props){
+ export function Comp() {
+ const props = useOutletContext();
return props.someProp;
}
history
+ import { history } from 'umi';
export default function Page(props) {
return (
<div onClick={()=>{
- props.history.push('list');
+ history.push('list');
}}>
</div>
);
}
location
建议组件或 hooks 里用 useLocation 取,其他地方就用 window.location 获取。
export default function Page(props) {
+ const { location } = window;
return (
<div>
- { props.location }
+ { location }
</div>
);
}
或者
+ import { useLocation } from 'umi';
export default function Page(props) {
+ let location = useLocation();
return (
<div>
- { props.location }
+ { location }
</div>
);
}
更多 Umi 相关 api
完成以上操作后,执行下 max dev,访问 http://localhost:8000,请验证所有功能都符合预期。
如果你的项目无法正常启动,你可能还需要做如下操作:
遇到的问题
location 中的 query 找不到?
location 中的 query 不再支持了,后续推荐用 search
- const { query } = history.location;
+ import { parse } from 'query-string';
+ const query = parse(history.location.search);
或者如下 config/config.ts ,这里要注意个别情况需要单独处理。
import { defineConfig, utils } from 'umi';
export default defineConfig({
...
// 启用 react-router 5 兼容模式
reactRouter5Compat: {},
// 让 history 带上 query
historyWithQuery: {},
...
})
开启layout,报错React is not defined
可通过升级 react 到 18 规避这个问题。(我这里只是调试发现的报错顺带升级到了react18)
自定义Layout获取路由信息
import { useAppData } from "umi";
...
const { clientRoutes } = useAppData()
...
clientRoutes是你已注册的路由表数据,转为菜单的时候需要自己过滤出需要显示的路由,此处可以参考layout
document.ejs 去哪了,如何自定义 HTML 模板
可通过插件Api中的modifyHTML或者config/config.js中配置externals和headScripts实现,后者适用于设置哪些模块不打包以及简单的脚本注入。
通过插件Api官网的api写的很详细了照着配置就行。
插件注册:
config/config.js中配置
{
// 默认注册${root}/plugin.ts
plugins: [require.resolve('你的自定义插件path')]
}
自定义插件
import { IApi } from 'umi';
export default (api: IApi) => {
api.modifyHTML(($) => {
$('head').append([
`<script src='https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js'></script>`,
`<script src='others...'></script>`
])
return $;
});
};