Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

想了解一下 umi 2 与 3 对路由组件处理的异同 #4425

Closed
yunsii opened this issue Apr 10, 2020 · 45 comments
Closed

想了解一下 umi 2 与 3 对路由组件处理的异同 #4425

yunsii opened this issue Apr 10, 2020 · 45 comments

Comments

@yunsii
Copy link
Contributor

yunsii commented Apr 10, 2020

之前有实现一个基于路由实现页面多标签页渲染的功能,大致的实现方式是在 ant design pro 中的 BasicLayout 中劫持 children 的渲染,基于路由保存当前的 children 并渲染。大概率是升级到 umi 3 后和 umi 2 对于路由组件的处理不同,导致了一个奇怪的问题。

分析 DOM 节点,发现 umi 3 下会看到所有打开的标签页在切换后都会渲染为切换后的标签页的 DOM,导致了即使是切换标签页,都会使得所有标签页卸载后再挂载新的页面 。相比于以前基于 umi 2 下的实现,每个标签页 DOM 节点都是各自页面的 DOM 节点,不存在 umi 3 那样的问题。

感觉是 Switch 组件的行为有了变化,不知道是 React-Router 还是 umi 导致的?

@yunsii
Copy link
Contributor Author

yunsii commented Apr 11, 2020

又折腾了一天,发现的确存在差异。repo: https://github.com/theprimone/ant-design-pro-plus

以 Welcome 和 Page Tabs Demo / Query 两个页面来分析。master 分支(使用的 umi 2.13)下实现的多标签页可实现性能优化。feat/umi3 分支下,几乎同样的标签页组件,只是使用了 useLocation 这个 hook 。在控制台可以看见当在这两个标签页之间切换的时候,前一个标签页会被卸载,分析 DOM 发现两个标签页会渲染成同一个页面。

@yunsii
Copy link
Contributor Author

yunsii commented Apr 12, 2020

以下分析有误,可跳过。

大概是知道原因了,看源码看得头大。

umi 2.13 使用的是 dva 里的 dynamic 加载的页面组件,umi 3 使用的是 umi 内部的 loadable 加载页面组件。又 dynamic 内部通过 state 保存加载的组件,如下图

image

loadable 是直接透传 children ,如下图

image

由此,应该可以得出结论:umi 2 中劫持 children 渲染的是 AsyncComponent 组件umi 3 中的 children 则是 Switch 组件,故导致在 umi 3 中,基于路由实现的标签页功能的所有标签页都会渲染为当前路由的页面,换言之,当切换标签页的时候就会导致所有标签页都会路由到当前的路径,未激活的标签页也会卸载之前的页面再加载当前路由的页面,性能堪忧。

@yunsii
Copy link
Contributor Author

yunsii commented Apr 15, 2020

之前还琢磨是动态加载组件的问题,原来是方向错了。其实仔细想想,动态加载组件也只是去加载组件而已啊 _(:D)∠)_


下面分享最新的调查结果:

umi 3 自己封装了一个 Switch ?去搜了一下

import { __RouterContext as RouterContext, matchPath } from '@umijs/runtime';

中的 __RouterContext 有何用。看到这么一个推 😂 猜测是因为这个使用的 context 中的 location 导致了路由切换时重新匹配路由再 React.cloneElement() 了?这也能解释为什么页面总是卸载之后再加载了。

umi 2.1.0 中 renderRoutes()childrenchildRoutes 。每个路由下的所有子路由再封装成 Switch 去渲染,这样或许能解释在 umi 2 下做的性能优化了?


以上分析尚不准确,参考最新结果

@eevin
Copy link

eevin commented May 4, 2020

同样的问题,升级umi3 后,之前使用tabs页签,缓存路由的方式已经失效,是否找到解决办法?
这是我现在最棘手的事,方便支持qiankun升级到了umi3。

@yunsii
Copy link
Contributor Author

yunsii commented May 4, 2020

@eevin 官方不改的话,还是退回 2.x 吧

@eevin
Copy link

eevin commented May 4, 2020

@zpr1g 从props.route 里面获取与pathname相对应的component,可以实现之前相同的效果。

@yunsii
Copy link
Contributor Author

yunsii commented May 4, 2020

@eevin 这样是直接调用 DynamicComponent 的话,那嵌套路由都得自己去遍历渲染了?思路好像是行得通的,我看看呢 😎

@k983551019
Copy link

我也遇到了,不行只能退回去了。

@yunsii
Copy link
Contributor Author

yunsii commented May 6, 2020

@k983551019 可以先暂时退回的,不过 @eevin 说的那个方案感觉是行得通的,我还没测试 😂

@k983551019
Copy link

老哥们有好的解决办法了吗,

@yunsii
Copy link
Contributor Author

yunsii commented May 18, 2020

应该是不会升级 umi 3 了

@yunsii
Copy link
Contributor Author

yunsii commented May 19, 2020

终于算是一个 bug 了吗 _(:D)∠)_ 有望升级 umi 3 了啊

@eevin
Copy link

eevin commented Jun 9, 2020

现在有个明显的性能问题,当以tabs方式打开过多页面时,切换的时候会出现明显卡顿。
由于顶层组件刷新,造成所有已渲染的函数组件都要执行2次,即使使用React.memo也没有用。

@yunsii
Copy link
Contributor Author

yunsii commented Jun 9, 2020

@eevin 我实现的这个方法,用来包裹页面组件是可行的 https://github.com/zpr1g/ant-design-pro-plus/blob/master/src/components/RouteTabs/utils.tsx#L189

@smali-li
Copy link

smali-li commented Nov 5, 2020

很期待这个问题的解决啊,不然一直不敢升级

@GitHub-FP
Copy link

GitHub-FP commented Nov 28, 2020

我找到一个解决办法,就是使用react-router的switch组件替换children实现多页签。react-router的Switch这个组件怎么使用大家可以在网上查下。
4FEE727C-74C0-4fbe-BCD6-EE0230863D4A
我是通过 #4827 的git项目观察得到的,感谢。

@yunsii
Copy link
Contributor Author

yunsii commented Nov 29, 2020

@GitHub-FP 确定可以?children 里边已经有一层 Switch 使用了 __RouterContext 来监听路由变化了啊,难道不会被强刷?

@GitHub-FP
Copy link

@theprimone 不会,因为使用react-router的switch组件替换掉了umi的Switch组件。__RouterContext也是导致不能使用多标签的原因。
我写了一个demo , 你可以clone下来看看。多个里面的Dom元素是不同的,切换了也没有进行元素的删除,应该是可行的。
地址:https://github.com/GitHub-FP/umi-page-multi-tab-template

@yunsii
Copy link
Contributor Author

yunsii commented Nov 29, 2020

@GitHub-FP 才注意到是通过 children.props.children 拿到的被 Umi Switch 包裹的 children 来使用 😂 好骚啊。这么一说是可以升级到 umi@3 了。另外已经被 @sorrycc 标记为 bug 了,那也不用等着修复了。

@eevin
Copy link

eevin commented Dec 1, 2020

@GitHub-FP

history.push('/hello/index')}>world
history.push('/hello/demo')}>hello
history.push('/')}>index

相同前缀路由,还是不得行的。

@yunsii
Copy link
Contributor Author

yunsii commented Dec 1, 2020

@eevin 我还没试过,这是啥意思?

@GitHub-FP
Copy link

@eevin 我改成了你写的路由也还是可以呀,可以详细的描述一下你的问题吗。我更新了下demo,你看下呢。
https://github.com/GitHub-FP/umi-page-multi-tab-template

@eevin
Copy link

eevin commented Dec 1, 2020

@GitHub-FP 没问题了,我这边嵌套路由写法不规范造成的。

@eevin
Copy link

eevin commented Dec 1, 2020

鼓捣了一下,还是没达到预期。
tab下切换确实不会重复渲染,但是点击Link的时候,还是会渲染的,而且是所有已渲染的tab 都会渲染一次,这个会很影响性能。如果开足够多的tab的时候,会出现卡顿情况。

暂时继续使用缓存路由组件的方式了。

@yunsii
Copy link
Contributor Author

yunsii commented Dec 1, 2020

点击 Link 会刷新所有的 tab 是之前都会存在的问题,hoc 一下应该就能解决了,这是我之前的办法 https://github.com/theprimone/ant-design-pro-plus/blob/master/src/components/RouteTabs/utils.tsx#L192

@thelaurelyy
Copy link

thelaurelyy commented Dec 14, 2020

不知道楼主是否看过生命周期的问题,就是在tabs切换的业务模块中监听 active 和 unactive ,给出钩子函数? @theprimone

@yunsii
Copy link
Contributor Author

yunsii commented Dec 14, 2020

这是对于多标签页功能的 feature?是的话是什么样的场景呢?

@thelaurelyy
Copy link

就是多标签页切换的时候,在每个功能panel中监听 active 和 unactive 响应事件

@yunsii
Copy link
Contributor Author

yunsii commented Dec 17, 2020

就是多标签页切换的时候,在每个功能panel中监听 active 和 unactive 响应事件

这个是功能上的需求嘛,我想了解一下你是有什么样的业务场景,是要在激活某个 panel 的时候刷新数据?

@thelaurelyy
Copy link

就是多标签页切换的时候,在每个功能panel中监听 active 和 unactive 响应事件

这个是功能上的需求嘛,我想了解一下你是有什么样的业务场景,是要在激活某个 panel 的时候刷新数据?

没错,其实是想模拟Angular中路由复用的生命周期函数 onReuseInit / OnreuseDestroy,用来做激活某个panel时刷新数据 或者 离开时回收订阅等;

@yunsii
Copy link
Contributor Author

yunsii commented Jan 11, 2021

没错,其实是想模拟Angular中路由复用的生命周期函数 onReuseInit / OnreuseDestroy,用来做激活某个panel时刷新数据 或者 离开时回收订阅等;

不回复我都忘了这个事儿了 😂 今晚回去就搞

@yunsii
Copy link
Contributor Author

yunsii commented Jan 11, 2021

没错,其实是想模拟Angular中路由复用的生命周期函数 onReuseInit / OnreuseDestroy,用来做激活某个panel时刷新数据 或者 离开时回收订阅等;

琢磨了一下,暂时没有太好的 API 设计思路,还得再想想

@thelaurelyy
Copy link

没错,其实是想模拟Angular中路由复用的生命周期函数 onReuseInit / OnreuseDestroy,用来做激活某个panel时刷新数据 或者 离开时回收订阅等;

琢磨了一下,暂时没有太好的 API 设计思路,还得再想想

好的,依旧感谢回复!

@sorrycc sorrycc closed this as completed Jan 14, 2021
@yunsii
Copy link
Contributor Author

yunsii commented Jan 31, 2021

今天花了一天时间深入调查实践了一下,发现即使使用 children.props.children 还是有些问题的,因为现在 Umi 把 Route 也重写了,直接从 __RouterContext 中取值,用是能用,但还是会导致一些奇怪的更新操作。

路由变化的时候 Umi Route 会更新并注入错误的 location 属性,因为使用了自定义的 React Router Switch 并给定了 location 时,Umi Route 并不会使用 props.location。

另外,因为注入了错误的 location 也使得我定义的 HOC withRouteTab 不能正常使用。

Umi@2.x 使用 Switch 时会自动注入 location,如下图,Umi@​3.x 没了不知道是 2 的 feature 还是 bug 😂

image

综上,还是得底层支持才是最好的。毕竟 React Router 中的 Switch 和 Route 都会处理 props.location 的。

@yunsii
Copy link
Contributor Author

yunsii commented Jan 31, 2021

@GitHub-FP 你现在这个做法应该会导致页面中有子路由时会通过新的标签页打开,并且丢失 parent component。

@yunsii yunsii mentioned this issue Jan 31, 2021
4 tasks
@GitHub-FP
Copy link

@GitHub-FP 你现在这个做法应该会导致页面中有子路由时会通过新的标签页打开,并且丢失 parent component。

是的,打开子路由会存在问题。我这边的情况是切换标签页时子路由标签页会出现是刷新,现在我是把子路由提出来和一级路由并列。当时我发现children.props.children里面好像没有子路由的路由信息,从而导致打开子路由会刷新,但是解释不了为什么子路由能打开的情况。

@yunsii
Copy link
Contributor Author

yunsii commented Jan 31, 2021

是的,打开子路由会存在问题。我这边的情况是切换标签页时子路由标签页会出现是刷新,现在我是把子路由提出来和一级路由并列。当时我发现children.props.children里面好像没有子路由的路由信息,从而导致打开子路由会刷新,但是解释不了为什么子路由能打开的情况。

我在我项目的做法是通过路由匹配得到一个 key,如果 children 变化得到的 key 相同时做特殊处理

@yunsii
Copy link
Contributor Author

yunsii commented Feb 2, 2021

@thelaurelyy 顺手发现 ProLayout 有 onPageChange 属性,应该可以实现你的需求。

image

@yunsii
Copy link
Contributor Author

yunsii commented Feb 16, 2021

又发现了一个问题,应该还是在 umi@​3.x 才出现的,如果设置路由切换动画的话,由于 Route 直接消费 __RouterContext 会导致卸载过程中的页面组件会渲染成当前路由的页面再被卸载掉 _(:3J∠)_

@yunsii
Copy link
Contributor Author

yunsii commented Feb 25, 2021

Nice,升级到 Umi@3.3.9 之后,标签页功能正常了 😃 参考 theprimone/ant-design-pro-plus。顺便通知一下两位 @eevin @GitHub-FP

@k983551019
Copy link

Nice,升级到 Umi@3.3.9 之后,标签页功能正常了 😃 参考 theprimone/ant-design-pro-plus。顺便通知一下两位 @eevin @GitHub-FP

现在不是3.3.8?

@yunsii
Copy link
Contributor Author

yunsii commented Mar 4, 2021

image

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

@946629031
Copy link

Umi 的文档确实不够详细啊,如果能给个 完整的项目代码 示例就好了。。。 关于路由问题,我研究了 快一周了,也没找到 特别好的解决方案

@yunsii
Copy link
Contributor Author

yunsii commented Aug 18, 2021

Umi 的文档确实不够详细啊,如果能给个 完整的项目代码 示例就好了。。。 关于路由问题,我研究了 快一周了,也没找到 特别好的解决方案

现在还有什么问题吗?我这个问题都涉及源码实现了,文档肯定不可能这么具体的。

@c-ujung
Copy link

c-ujung commented Aug 31, 2021

一样遇到了,我是BasicLayout.js中使用tabs组件来实现的,基于路由缓存children来对比渲染,还好是旧项目,目前没有遇到迭代升级需求,但是后面估计跑不掉。现在貌似可以用umi-plugin-keep-alive来实现

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants