umi max+antd4 实现多tab布局,切换tab页路由不刷新

84 阅读3分钟

需求背景

在公司开发后台管理系统,系统的基础布局是通过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页切换不用重新刷新的问题了。