之前有实现一个基于路由实现页面多标签页渲染的功能,大致的实现方式是在 ant design pro 中的 BasicLayout 中劫持 children 的渲染,基于路由保存当前的 children 并渲染。大概率是升级到 umi 3 后和 umi 2 对于路由组件的处理不同,导致了一个奇怪的问题。
分析 DOM 节点,发现 umi 3 下会看到所有打开的标签页在切换后都会渲染为切换后的标签页的 DOM,导致了即使是切换标签页,都会使得所有标签页卸载后再挂载新的页面 。相比于以前基于 umi 2 下的实现,每个标签页 DOM 节点都是各自页面的 DOM 节点,不存在 umi 3 那样的问题。
感觉是 Switch 组件的行为有了变化,不知道是 React-Router 还是 umi 导致的?
又折腾了一天,发现的确存在差异。repo: https://github.com/theprimone/ant-design-pro-plus
以 Welcome 和 Page Tabs Demo / Query 两个页面来分析。master 分支(使用的 umi 2.13)下实现的多标签页可实现性能优化。feat/umi3 分支下,几乎同样的标签页组件,只是使用了 useLocation 这个 hook 。在控制台可以看见当在这两个标签页之间切换的时候,前一个标签页会被卸载,分析 DOM 发现两个标签页会渲染成同一个页面。
以下分析有误,可跳过。
大概是知道原因了,看源码看得头大。
umi 2.13 使用的是 dva 里的 dynamic 加载的页面组件,umi 3 使用的是 umi 内部的 loadable 加载页面组件。又 dynamic 内部通过 state 保存加载的组件,如下图

loadable 是直接透传 children ,如下图

由此,应该可以得出结论:umi 2 中劫持 children 渲染的是 AsyncComponent 组件,umi 3 中的 children 则是 Switch 组件,故导致在 umi 3 中,基于路由实现的标签页功能的所有标签页都会渲染为当前路由的页面,换言之,当切换标签页的时候就会导致所有标签页都会路由到当前的路径,未激活的标签页也会卸载之前的页面再加载当前路由的页面,性能堪忧。
之前还琢磨是动态加载组件的问题,原来是方向错了。其实仔细想想,动态加载组件也只是去加载组件而已啊 _(:D)∠)_
下面分享最新的调查结果:
umi 3 自己封装了一个 Switch ?去搜了一下
import { __RouterContext as RouterContext, matchPath } from '@umijs/runtime';
中的 __RouterContext 有何用。看到这么一个推 😂 猜测是因为这个使用的 context 中的 location 导致了路由切换时重新匹配路由再 React.cloneElement() 了?这也能解释为什么页面总是卸载之后再加载了。
umi 2.1.0 中 renderRoutes() 。children 是 childRoutes 。每个路由下的所有子路由再封装成 Switch 去渲染,这样或许能解释在 umi 2 下做的性能优化了?
以上分析尚不准确,参考最新结果
同样的问题,升级umi3 后,之前使用tabs页签,缓存路由的方式已经失效,是否找到解决办法?
这是我现在最棘手的事,方便支持qiankun升级到了umi3。
@eevin 官方不改的话,还是退回 2.x 吧
@zpr1g 从props.route 里面获取与pathname相对应的component,可以实现之前相同的效果。
@eevin 这样是直接调用 DynamicComponent 的话,那嵌套路由都得自己去遍历渲染了?思路好像是行得通的,我看看呢 😎
我也遇到了,不行只能退回去了。
@k983551019 可以先暂时退回的,不过 @eevin 说的那个方案感觉是行得通的,我还没测试 :joy:
老哥们有好的解决办法了吗,
应该是不会升级 umi 3 了
终于算是一个 bug 了吗 _(:D)∠)_ 有望升级 umi 3 了啊
现在有个明显的性能问题,当以tabs方式打开过多页面时,切换的时候会出现明显卡顿。
由于顶层组件刷新,造成所有已渲染的函数组件都要执行2次,即使使用React.memo也没有用。
@eevin 我实现的这个方法,用来包裹页面组件是可行的 https://github.com/zpr1g/ant-design-pro-plus/blob/master/src/components/RouteTabs/utils.tsx#L189
很期待这个问题的解决啊,不然一直不敢升级
我找到一个解决办法,就是使用react-router的switch组件替换children实现多页签。react-router的Switch这个组件怎么使用大家可以在网上查下。

我是通过 #4827 的git项目观察得到的,感谢。
@GitHub-FP 确定可以?children 里边已经有一层 Switch 使用了 __RouterContext 来监听路由变化了啊,难道不会被强刷?
@theprimone 不会,因为使用react-router的switch组件替换掉了umi的Switch组件。__RouterContext也是导致不能使用多标签的原因。
我写了一个demo , 你可以clone下来看看。多个
地址:https://github.com/GitHub-FP/umi-page-multi-tab-template
@GitHub-FP 才注意到是通过 children.props.children 拿到的被 Umi Switch 包裹的 children 来使用 😂 好骚啊。这么一说是可以升级到 umi@3 了。另外已经被 @sorrycc 标记为 bug 了,那也不用等着修复了。
@GitHub-FP
相同前缀路由,还是不得行的。
@eevin 我还没试过,这是啥意思?
@eevin 我改成了你写的路由也还是可以呀,可以详细的描述一下你的问题吗。我更新了下demo,你看下呢。
https://github.com/GitHub-FP/umi-page-multi-tab-template
@GitHub-FP 没问题了,我这边嵌套路由写法不规范造成的。
鼓捣了一下,还是没达到预期。
tab下切换确实不会重复渲染,但是点击Link的时候,还是会渲染的,而且是所有已渲染的tab 都会渲染一次,这个会很影响性能。如果开足够多的tab的时候,会出现卡顿情况。
暂时继续使用缓存路由组件的方式了。
点击 Link 会刷新所有的 tab 是之前都会存在的问题,hoc 一下应该就能解决了,这是我之前的办法 https://github.com/theprimone/ant-design-pro-plus/blob/master/src/components/RouteTabs/utils.tsx#L192
不知道楼主是否看过生命周期的问题,就是在tabs切换的业务模块中监听 active 和 unactive ,给出钩子函数? @theprimone
这是对于多标签页功能的 feature?是的话是什么样的场景呢?
就是多标签页切换的时候,在每个功能panel中监听 active 和 unactive 响应事件
就是多标签页切换的时候,在每个功能panel中监听 active 和 unactive 响应事件
这个是功能上的需求嘛,我想了解一下你是有什么样的业务场景,是要在激活某个 panel 的时候刷新数据?
就是多标签页切换的时候,在每个功能panel中监听 active 和 unactive 响应事件
这个是功能上的需求嘛,我想了解一下你是有什么样的业务场景,是要在激活某个 panel 的时候刷新数据?
没错,其实是想模拟Angular中路由复用的生命周期函数 onReuseInit / OnreuseDestroy,用来做激活某个panel时刷新数据 或者 离开时回收订阅等;
没错,其实是想模拟Angular中路由复用的生命周期函数 onReuseInit / OnreuseDestroy,用来做激活某个panel时刷新数据 或者 离开时回收订阅等;
不回复我都忘了这个事儿了 😂 今晚回去就搞
没错,其实是想模拟Angular中路由复用的生命周期函数 onReuseInit / OnreuseDestroy,用来做激活某个panel时刷新数据 或者 离开时回收订阅等;
琢磨了一下,暂时没有太好的 API 设计思路,还得再想想
没错,其实是想模拟Angular中路由复用的生命周期函数 onReuseInit / OnreuseDestroy,用来做激活某个panel时刷新数据 或者 离开时回收订阅等;
琢磨了一下,暂时没有太好的 API 设计思路,还得再想想
好的,依旧感谢回复!
今天花了一天时间深入调查实践了一下,发现即使使用 children.props.children 还是有些问题的,因为现在 Umi 把 Route 也重写了,直接从 __RouterContext 中取值,用是能用,但还是会导致一些奇怪的更新操作。
路由变化的时候 Umi Route 会更新并注入错误的 location 属性,因为使用了自定义的 React Router Switch 并给定了 location 时,Umi Route 并不会使用 props.location。
另外,因为注入了错误的 location 也使得我定义的 HOC withRouteTab 不能正常使用。
[email protected] 使用 Switch 时会自动注入 location,如下图,Umi@3.x 没了不知道是 2 的 feature 还是 bug 😂

综上,还是得底层支持才是最好的。毕竟 React Router 中的 Switch 和 Route 都会处理 props.location 的。
@GitHub-FP 你现在这个做法应该会导致页面中有子路由时会通过新的标签页打开,并且丢失 parent component。
@GitHub-FP 你现在这个做法应该会导致页面中有子路由时会通过新的标签页打开,并且丢失 parent component。
是的,打开子路由会存在问题。我这边的情况是切换标签页时子路由标签页会出现是刷新,现在我是把子路由提出来和一级路由并列。当时我发现children.props.children里面好像没有子路由的路由信息,从而导致打开子路由会刷新,但是解释不了为什么子路由能打开的情况。
是的,打开子路由会存在问题。我这边的情况是切换标签页时子路由标签页会出现是刷新,现在我是把子路由提出来和一级路由并列。当时我发现children.props.children里面好像没有子路由的路由信息,从而导致打开子路由会刷新,但是解释不了为什么子路由能打开的情况。
我在我项目的做法是通过路由匹配得到一个 key,如果 children 变化得到的 key 相同时做特殊处理
@thelaurelyy 顺手发现 ProLayout 有 onPageChange 属性,应该可以实现你的需求。

又发现了一个问题,应该还是在 umi@3.x 才出现的,如果设置路由切换动画的话,由于 Route 直接消费 __RouterContext 会导致卸载过程中的页面组件会渲染成当前路由的页面再被卸载掉 _(:3J∠)_
Nice,升级到 [email protected] 之后,标签页功能正常了 😃 参考 theprimone/ant-design-pro-plus。顺便通知一下两位 @eevin @GitHub-FP
Nice,升级到 [email protected] 之后,标签页功能正常了 😃 参考 theprimone/ant-design-pro-plus。顺便通知一下两位 @eevin @GitHub-FP
现在不是3.3.8?

@k983551019 特地看了看 yarn.lock 上次我弄的时候确实是 3.3.9 了,再看了看 npm 包的发布时间,可能 git 仓库后打包的吧 😂