umi icon indicating copy to clipboard operation
umi copied to clipboard

[Feature Request] patchClientRoutes 相关动态路由方案讨论

Open xiaohuoni opened this issue 3 years ago • 5 comments

问题

umi3

umi@3 中 如下编写可用。

这是页面组件,这个文件可能是子包中编译后的页面组件。

const ListPage = (props)=>{
const { location,defaultProps} = props;
const { query } = location;
// 这里不仅能取到 location 还能取到 patchClientRoutes 中传入的 defaultProps
}

这是 app.ts

let extraRoutes: any[];

function findItem<T = any>(
  target: T[],
  callback: (item: T, index: number, list: T[]) => boolean,
) {
  const list = target;
  // Makes sures is always has an positive integer as length.
  // eslint-disable-next-line
  const length = list.length >>> 0;
  // eslint-disable-next-line
  const thisArg = arguments[1];
  for (let i = 0; i < length; ) {
    const element = list[i];
    if (callback.call(thisArg, element, i, list)) {
      return element;
    }
    i += 1;
  }
  return null;
}

export function patchClientRoutes({ routes }) {
  console.log(routes);
  // 根据 extraRoutes 对 routes 做一些修改
  // 这个方法就是复用 list 的 componet 来作为所有动态路由的 dom,也可以通过匹配来获取对应的 componet
  const target = findItem(routes[0].routes, (item) => item.path === 'ListPage');
  console.log(target);
  extraRoutes.forEach((item) => {
    routes[0].routes.push({
      ...target,
      path: item.pagePath,
      defaultProps: item,
      id: item.pageId,
    });
  });
}

export function render(oldRender) {
  extraRoutes = [
    {
      pageId: '873800137042182144',
      pageVersionId: '873800137042182145',
      pageName: '组件测试',
      pagePath: '/ceshi1751',
      pageDynamicFlag: false,
    },
  ];
  oldRender();
}

umi4

umi@4 要支持同样的内容的话

需要在当前项目中新建一个文件,如 pages/abc

import { ListPage } from 'xxxxxx';
import { useLocation, useRouteData } from 'umi';
// @ts-ignore
import { parse } from 'qs';

export default ({}) => {
  const location = useLocation();
  const route = useRouteData();
  var query = parse(location.search, {
    ignoreQueryPrefix: true,
  });
  return <ListPage location={{ ...location, query }} route={route} />;
};

patchClientRoutes中 需要对 element 做 clone 处理,塞入我们需要的数据。

export function patchClientRoutes({ routes }: any) {
  // 请注意这里是找到当前项目中的 abc
  const target = findItem(routes[0].routes[0].routes, (item) => item.path === 'abc');
  dRoutes.forEach((item) => {
    routes[0].routes[0].routes.push({
      ...target,
      // 需要在这里将 defaultProps 塞到 props 的 value 里面,才能在页面中
      element: React.cloneElement(target.element, {value:{ defaultProps: item?.defaultProps }}),
      path: item?.defaultProps?.pagePath,
      // 下面这个 defaultProps 是完全无用的。
      defaultProps: item,
      id: item?.defaultProps?.pageId,
    });
  });
  console.log(routes[0].routes[0].routes);
}

很多操作差异: 1、需要push 的对象,可能是 routes[0].routes routes[0].routes[0].routes routes[0].routes[0].routes[0].routes 这取决于用了几层 layout 2、 route item 中传入的属性对象,不会传到组件中。需要塞到 props 的 value 对象中,才能在页面中通过 useRouteData 取到数据,但是会丢失原来的 value 中的值,在 patchClientRoutes 无法获取到开始的值。也可能需要把原来的 path 啥的数据,放到 value 里面。 3、路由组件 props 一直是空对象,需要的值都从 use hooks 取,所以需要再建一个文件,来做属性透传。 4、patchClientRoutes 中的 element 如果不是 Routes 中的 files 对应的组件,就算 element 是一个正确的值,也是无法使用的。(这点需要进一步确认原因)。重现步骤比较长,那就是将 patchClientRoutes 这个方法放到子包里面,导出给当前项目使用。findItem 找到的是子包项目编译后的 component。所以当前项目中 push 了 ClientRoutes ,没有处理 Routes

可能的解法

1、路由组件 props 空对象

默认对路由组件的 element 进行修改,默认包裹一层 withRouter 将丢失的对象补齐。因为需要用到 patchClientRoutes 时才塞进去的数据,到 route 属性中,所以这个操作要很滞后。

2、动态路由组件需要被编译

一般使用约定式路由,所有的组件都有编译。如果使用配置式路由,会有路由文件丢失的情况。所以需要再提供一个对象,类似

const routes = {
	xxx: React.lazy(()=>import('@/pages/xxx'))
}

3、routes[0].routes 这个不处理,因为用户要塞到哪一层,是比较自由的

4、umi3 中 component 可以是字符串,回去匹配已经编译后的文件。现在 push 进去的 element 是加载后的对象。可以保留旧的 component 字段,在渲染前的 useRoutes 这边需要处理一下 component 字段。

暂时能想到的问题就这些,欢迎补充。

xiaohuoni avatar Aug 18 '22 06:08 xiaohuoni

我在这里写了一个动态路由一种解法的 demo :https://github.com/umijs/umi/discussions/9717#discussioncomment-4078053

fz6m avatar Nov 12 '22 01:11 fz6m

请教一下,从服务端获取的动态路由,怎么配置 路由权限呢,静态路由直接 wrappers: ['@/wrappers/Auth'], 就好了,动态路由,我尝试了下 route.wrappers = [ React.createElement(require('@/wrappers/Auth').default), ]; 但是不行 呢 @xiaohuoni xiaohuoni

lqliqi avatar Dec 01 '22 13:12 lqliqi

请教一下,从服务端获取的动态路由,怎么配置 路由权限呢,静态路由直接 wrappers: ['@/wrappers/Auth'], 就好了,动态路由,我尝试了下 route.wrappers = [ React.createElement(require('@/wrappers/Auth').default), ]; 但是不行 呢 @xiaohuoni xiaohuoni

怎么没人回答

programmer-jiangtao avatar Apr 19 '23 09:04 programmer-jiangtao

人工添加的时候上一层校验权限的 HOC 就行了,另外不推荐用 require ,应该用 Suspense + lazy

// 声明全部的路由映射
const routesMap = {
  'xxx': React.lazy(() => import('./path/to/component')),
  // ...
}

// 动态添加路由表,包裹 HOC

routes.push(
  <Suspense fallback={<div>loading...</div>}>
    <AuthContext>{route}</AuthContext>
  </Suspense>
)
``

fz6m avatar Apr 19 '23 13:04 fz6m

人工添加的时候上一层校验权限的 HOC 就行了,另外不推荐用 require ,应该用 Suspense + lazy

// 声明全部的路由映射
const routesMap = {
  'xxx': React.lazy(() => import('./path/to/component')),
  // ...
}

// 动态添加路由表,包裹 HOC

routes.push(
  <Suspense fallback={<div>loading...</div>}>
    <AuthContext>{route}</AuthContext>
  </Suspense>
)
``

大大,如何能动态生成routesMap,不用手动录入

52myu avatar Jan 02 '24 02:01 52myu