需求背景
在公司开发后台管理系统,系统的基础布局是通过tab页操作路由切换。由于页面内容上存在表单内容,所以需要切换tab页的时候页面不要重新刷新。
之前系统的框架是umi3.x版本,解决方案是这样实现的:
设置destroyOnHidden属性为false,然后直接把Route组件放在TabPane组件内部。
import { Route } from "react-router-dom";
<ProLayout
breadcrumbRender={(routers = []) => {}}
{...props}
>
<Tabs
destroyOnHidden={false}
{...otherProps}
>
{pages.map((page) => (
<TabPane tab={page.label} key={page.key} closable={true}>
<Route component={page.component} exact={true} />
</TabPane>
))}
</Tabs>
</ProLayout>
其中最关键的 pages数组以及page.component属性是如何获取的呢?
breadcrumbRender 这个函数,ProLayout会自动传入一个routers数组,里面就是我们需要的处理过后的路由信息以及对应的各个组件。我们是在这个函数中遍历routers,拿到目标路由数据。
pages.push({ key: route.path, label: route.breadcrumbName, component: route.component, });
这是一个很方便实现目标效果的办法。
Umi框架升级之后遇到问题
最近把系统的框架从umi3.x版本升级到umi max了。同样的解决方案已经失效了。由于react-router-dom组件版本也同时升级,导致Route组件不支持直接包裹在Tab组件内部使用,会报错。
同时,ProLayout里面breadcrumbRender、menuDataRender等方法,它内部传入的routes参数,其中的component属性不是一个真正的组件对象了,而变成了我们route.config.js里面定义的组件路径了,也就是一个字符串。所以不能直接拿来当子组件渲染。
解决方案:怎么拿到实际的路由组件
关于解决方案,我在网上搜索了很长时间,能找到的实现方案看上去都很复杂。我试了一下根据路由路径使用React.lazy引入组件的,但是会出现无穷嵌套渲染的问题。如果尝试路由keep-alive,也需要去手动配置插件。最后我还是找到了一个比较简便、不需要额外配置,也不需要改动整体路由的方案。
通过查找umi的文档,发现了patchRoutes这个运行时配置函数。在这个函数里能拿到实际的路由组件对象。 把所有的路由组件和它们的path关联起来,形成一个映射,然后放在全局环境中。
/**
* umi运行时配置。src/app.js
*/
export function patchRoutes({ routes, routeComponents }) {
let allRoutesMap = {};
//console.log(routes, routeComponents);
for (let key in routes) {
allRoutesMap[routes[key].path] = routeComponents[key];
}
window.allRoutesMap = allRoutesMap;
// console.log("patchRoutes", routes, routeComponents);
}
在Layout组件中使用这个全局对象:
//每次location变化时运行这个函数,如果发现当前的tabList不包含当前location.pathname,才需要更新这个tabList
useEffect(() => {
let flag = !tabList.some(
(item) => item.key == location.pathname + location.search
); //tabs里面没有该页面
if (location.pathname && flag) {
const Comp = window.allRoutesMap[location.pathname];
let newList = [...tabList];
//routes是路由配置数据,经过某些其他的特殊化处理得到的
let target = routes.filter((item) => {
return item.path == location.pathname;
});
if (target.length > 0) {
newList.push({
...target[0],
path: target[0].path + location.search,
key: target[0].key + location.search,
label: target[0].name,
children: <Comp />, //这里可对Comp进行权限校验,此处省略
forceRender: true,
});
setTabList(newList);
}
}
}, [location, routes]);
// 在ProLayout组件下面包裹:
<Tabs
hideAdd
destroyInactiveTabPane={false}
type="editable-card"
activeKey={pageKey}
onChange={onTabChange}
onEdit={onTabEdit}
tabBarExtraContent={operations}
items={[...tabList]}
>
</Tabs>
如此就可以轻松实现多个tab页切换不用重新刷新的问题了。