问题记录之前端路由系统

概要:

公司的一个项目中使用了根据路由配置生成对应的Route,而配置会存在一份在store中,当store中的RouteConfig变化时,会根据最新的配置来生成最新的试图。

因为路由配置系统实现上的一些缺陷本次需要对其就行性能上的一些优化,优化后的路由系统在运行时偶尔会导致页面白屏,在排查后总结一下问题导致的原因和问题的解决方案,和过程中的一些思考,以便后期回归。

背景:

在业务系统中我们的路由方案采用了根据文件夹格式生成的方式,例如系统中存在文件夹:

/home
/plantform
--/list
--/detail
--/create
/system-setting
--/banner-setting
--/notice-setting

那么系统中就会存在对应的路由配置FullRouteConfig

[{id: 'home',path: '/home',component: Home,},{id: 'plantform',path: '/plantform',component: Home,children: [{id: 'plantform_list',path: 'list',component: HomeList,},{id: 'plantform_detail',path: 'detail',component: HomeDetail,},{id: 'plantform_create',path: 'create',component: HomeCreate,}]},{id: 'system_setting',path: '/system-setting',component: SystemSetting,children: [{id: 'system_setting_banner_setting',path: 'banner-setting',component: BannerSetting,},{id: 'system_setting_notice_setting',path: 'notice-setting',component: NoticeSetting,},]},
]

有了整个网站的FullRouteConfig 根据接口返回权限数据UseableMenus可以diff出当前用户具有的路由权限UseableRouteConfig

接口返回的权限数据UseableMenus

[{id: 'home',},{id: 'plantform',children: [{id: 'plantform_list',}]},{id: 'system_setting',children: [{id: 'system_setting_banner_setting',},{id: 'system_setting_notice_setting',},]},

那么UseableRouteConfig 的数据就是:

[{id: 'home',path: '/home',component: Home,},{id: 'home',path: '/plantform',component: Home,children: [{id: 'home_list',path: 'list',component: HomeList,},]},{id: 'system_setting',path: '/system-setting',component: SystemSetting,children: [{id: 'system_setting_notice_setting',path: 'notice-setting',component: NoticeSetting,},]},
]

没有权限的plantform路由下的两个路由就被删掉了:

[{id: 'plantform_list',path: 'list',component: HomeList,},{id: 'plantform_detail',path: 'detail',component: HomeDetail,},
]

到这里基本的路由方案已经完成,但还是不够完善,在实际使用中,很多业务系统由于过于庞大会采用根据路由分包的手段来对项目进行项目进行优化。采用分包优化后我们的FullRouteConfig 中的路由配置就会不完整,例如:

[...,{id: 'dynamic',path: '/dynamic/*',compnent: React.lazy(import('./dynamic')),children: [],}
]

在路由dynamic/*下其实是有children但是因为在文件’./dynamic’中,所以静态定义不出来,需要等到代码运行的时候才能将文件’./dynamic’中的路由补上。

‘./dynamic’:

const dynamicRouteConfig = [{id: 'dynaimc_crate',path: 'create',compnent: DynamicCreate,children: [],}
]// 将自身子路由dynamicRouteConfig更新到对应的父路由dynamic下
updateUseableRouteConfig('dynamic', dynamicRouteConfig)export default function Dynamic(props) {return props.children
}

所以如果接口中有对应路由权限,那么完整的UseableRouteConfig如下:

[{id: 'home',path: '/home',component: Home,},{id: 'home',path: '/plantform',component: Home,children: [{id: 'home_list',path: 'list',component: HomeList,},]},{id: 'system_setting',path: '/system-setting',component: SystemSetting,children: [{id: 'system_setting_notice_setting',path: 'notice-setting',component: NoticeSetting,},]},// --------  以下是children是动态添加的  ---------{id: 'dynamic',path: '/dynamic/*',compnent: React.lazy(import('./dynamic')),children: [{id: 'dynaimc_crate',path: 'create',compnent: DynamicCreate,children: [],}],}
]

为什么动态路由的更新是作用在UseableRouteConfig 上而不是FullRouteConfig 上?
可能是当时脑子抽了,想着FullRouteConfig + UseableMenus + DynamicRouteConifg = UseableRouteConfig 这个思路本身没问题,但确实导致了意想不到的问题。

所以到这里页面初始化流程是:加载页面后执行js得到FullRouteConfig (动态加载的RouteConfig不在其中),通过接口获取菜单权限信息,生成UseableRouteConfig ,根据UseableRouteConfig 生成对应页面,如果是动态路由对应的页面(dynamic),加载分包文件,然后动态注册路由,将动态路由更新到UseableRouteConfig 上。

所以视图的更新是响应UseableRouteConfig 变化的,会随着UseableRouteConfig 的变化而变化。

到这里路由系统工作的很正常,没有问题。

要做什么?

从上面的流程中可以发现页面渲染需要等到接口菜单权限UseableMenus返回后和FullRouteConfig 一起计算出UseableRouteConfig 然后开始渲染页面。所以接口请求和页面渲染是串行的,对性能有一定损耗,我们希望可以将流程改为并行,将权限接口请求的时间节省出来,让页面更快响应。并且经过评估用户权限并不会频繁变更,通过缓存用户菜单权限可以做到。

首先,我们将权限接口返回的数据缓存在localStorage中,在进入网站后去localStorage中读取,如果有则先试用改数据,同时请求对应的权限接口,接口返回后将数据和localStorage中的数据就行对比,如果不相等,使用最新的权限数据UseableMenus根据FullRouteConfig 重新计算UseableRouteConfig 在根据这个结果渲染对应页面。

遇到的问题?

上面的方案看似没有问题,实现之后我们发现当我切换用户登录后刷新网站有的时候会白屏,而有的时候则不会。这个奇怪的现象一开始让我们摸不着头脑,经过反复尝试后发现在动态加载的路由下偶尔可以复现这个问题,而在非动态加载的路由下则没有这个问题。

问题分析

  1. 网页切换用户后刷新老页面会白屏(对应路由的组件没有渲染)
  2. 复现问题的路由是动态加载的路由
  3. 问题并非稳定复现,有时候有,有时候没有

用户切换登录,代码执行流程为:下载js得到FullRouteConfig (这里的路由不完整,动态路由要代码执行的时候才能获取到),根据缓存的权限数据UseableMenus计算出UseableRouteConfig ,根据UseableRouteConfig 渲染页面,发现是动态加载的路由,执行代码将动态加载的路由注入到UseableRouteConfig 中。在前面获取缓存的权限数据的同时发送请求寻找最新的权限数据。因为切换了登录用户,所以缓存数据一定是失效的需要根据新的UseableMenusFullRouteConfig重新计算UseableRouteConfig

在这个过程中是分包的文件先下载并执行完成还是最新的权限数据UseableMenus 下载并执行完成和网络速度有关,并不能保证先后顺序。

如果是新的权限数据先返回就会根据FullRouteConfigUseableMenus 计算出一个有权限的FullRouteConfig 配置也就是UseableRouteConfig,然后动态路由返回并执行后会将DynamicRouteConfig 依据parentId注入到UseableRouteConfig 中形成完整的UseableRouteConfig

如果是动态路由分包文件的**DynamicRouteConfig先返回那么之后返回的UseableMenus 会导致重新根据FullRouteConfig 去计算UseableRouteConfig 而又因为DynamicRouteConfig并不在FullRouteConfig 上,那么计算出来的UseableRouteConfig 自然没有后期注入的DynamicRouteConfig,就导致页面白屏(白屏指的是路由对应的组件没有渲染)。

因为动态路由将自己挂载到UseableRouteConfig只会在文件被加载并执行的时候做一次,所以如果做过之后,UseableRouteConfig 又重新计算了,那么之前做的事情就相当于被冲掉了,因为FullRouteConfig 没有DynamicRouteConfig 这份数据。

例如:

FullRouteConfig: a, b, c, d

Cached**UseableMenus: a, b, dynamic, c

UseableRouteConfig : a, b, c

dynamicRouteConfig: parentId: b, id: dynamic. 和 UseableRouteConfig : a, b, c 计算之后:

UseableRouteConfig : a, b, b>dynamic, c

NewUseableMenus : a, b, dynamic, c, d

FullRouteConfig a, b, c, d 计算之后:

UseableRouteConfig : a, b, b, c

可以看到dynamic没了,这就是问题的根本原因。

解决问题

到这里问题的原因已经解释清楚了,就是动态路由的挂载在新的权限数据获取之前完成,导致新的权限数据获取到之后将挂载的动态路由数据冲掉了。

核心问题就是新的权限数据获取到跟新UseableRouteConfig 的时候需要把dynamicRouteConfig从老的UseableRouteConfig 中挑出来,然后更新到新的UseableRouteConfig 上。

  1. 使用新的权限数据和老的UseableRouteConfig 计算出一个UseableRouteConfig1
  2. 使用新的权限数据和FullRouteConfig 计算出一个新的UseableRouteConfig2
  3. UseableRouteConfig1 上存在但是UseableRouteConfig2 上不存在的菜单复制到UseableRouteConfig2
  4. UseableRouteConfig2 作为新的 UseableRouteConfig

为什么UseableRouteConfig1 不能作为新的UseableRouteConfig ?因为UseableRouteConfig1 中会缺少新权限数据中放开的路由配置,因为它们被之前过滤掉了,所以还是要以UseableRouteConfig2 为准,将UseableRouteConfig2 中没有UseableRouteConfig1 中有的复制到UseableRouteConfig2 即可。

其他更好的方案:

  1. 动态路由不要更新到UseableRouteConfig 而是更新到FullRouteConfig 这也是合理的,因为DynamicRouteConfig是从FullRouteConfig 中拆分出去的。等FullRouteConfig被完善后触发一次重新计算UseableRouteConfig 即可
  2. 不要将DynamicRouteConfig更新到UseableRouteConfig 而是在对应的组件下直接渲染,这样就是真正的响应式,而不是饶了一大圈,先更新UseableRouteConfig 然后再根据UseableRouteConfig 重新渲染试图,导致数据被冲掉。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/361890.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue兼容ie10问题并且node——module中出现es6语法如何解决

一、首先进行安装babel-polyfill,如果你用yarn安装babel-polyfill的话需要yarn add babel-polyfill进行安装 二、在babel.config.js中加入 三、在ie浏览器中找到报错的文件,然后将文件加入其中 转载于:https://www.cnblogs.com/changhuanran/p/11193149.…

2个在Java中将Byte []数组转换为String的示例

将字节数组转换为String似乎很容易,但是很难做到正确。 每当字节转换为String或char时,许多程序员都会犯忽略字符编码的错误,反之亦然。 作为程序员,我们都知道计算机只能理解二进制数据,即0和1。我们看到和使用的所有…

Linux文件IO-例会笔记总结

上周日实验室例会主要涉及linux文件操作的内核实现。主要讨论了linux下对文件进行操作时,系统内部调用了那些函数以及它们是怎么相互配合的。 linux系统是怎样对不同介质和不同的文件系统提供统一的文件操作接口呢?答案是:VFS。系统中所有文件…

js算法初窥03(搜索及去重算法)

前面我们了解了一些常用的排序算法,那么这篇文章我们来看看搜索算法的一些简单实现,我们先来介绍一个我们在实际工作中一定用到过的搜索算法——顺序搜索。 1、顺序搜索 其实顺序搜索十分简单,我们还是以第一篇文章写好的架子作为基础&#…

nginx try_files流程解析

前端部署单页应用时在nginx上经常用到try_files指令,而对于try_files并不知道其所以然,所以花时间整理总结如下。 Syntax: try_files file … uri; try_files file … code; Default: — Context: server, location 根据root和alias指令提供的值按照tr…

javascript中编码与解码的decodeURI()、decodeURIComponent()区别

1、 定义和用法 decodeURI() 函数可对 encodeURI() 函数编码过的 URI 进行解码。decodeURIComponent() 函数可对 encodeURIComponent() 函数编码的 URI 进行解码。 从W3C的定义和用法来看,两者没有什么区别,但是两者的参数是有区别的:decodeU…

vb 类模拟 引用

引用:http://wenku.baidu.com/view/f434ea26a5e9856a56126008.html Class1中 Option Explicit Public Sub test() Form1.TextForIpAddressAdd.Text "123"End Sub Form1中 Option Explicit Private test As New Class1 Private Sub Form_Load() 初始化 te…

用js来实现那些数据结构12(散列表)

上一篇写了如何实现简单的Map结构,因为东西太少了不让上首页。好吧。。。 这一篇文章说一下散列表hashMap的实现。那么为什么要使用hashMap?hashMap又有什么优势呢?hashMap是如何检索数据的?我们一点一点的来解答。 在我们学习一门…

如何自定义Hibernate脏检查机制

介绍 在上一篇文章中,我描述了Hibernate自动脏检查机制。 尽管您应该始终喜欢它,但是有时您可能想添加自己的自定义污垢检测策略。 自定义脏检查策略 Hibernate提供以下定制机制: 休眠拦截器#findDirty() CustomEnt…

读vue【深入响应式系统】后整理

一直以来对vue的依赖自动追踪的原理很感兴趣,像魔法一样。对于交给vue的对象返回后,在哪里使用了这个返回的对象vue会自动追踪,等这个对象改变时vue会自动通知到之前使用改变量的方法,整个过程和react很不一样,react的…

萌新自我介绍

第一次用博客,多有不会,可能向各位大佬请教,谢谢!!!!转载于:https://www.cnblogs.com/fakerOrz/p/11194872.html

使用select一个表更新另一个表(批量更新)

update a set a2b.b2, a3b.b3, ... from b where a.a1b.b1 转载于:https://www.cnblogs.com/haver/articles/2244740.html

用js来实现那些数据结构06(队列)

其实队列跟栈有很多相似的地方,包括其中的一些方法和使用方式,只是队列使用了与栈完全不同的原则,栈是后进先出原则,而队列是先进先出(First In First Out)。 一、队列 队列是一种特殊的线性表&#xff0c…

探索SwitchYard 2.0.0.Alpha2快速入门

在我的最后一篇文章中,我解释了如何在WildFly 8.1上使用SwitchYard。 同时,该项目很忙,并发布了另一个Alpha2。 这是一个很好的机会,在这里浏览快速入门并刷新您的记忆。 除了版本更改之外,您仍然可以使用较早的博客来…

MySQL之触发器

二:触发器 1. 什么是触发器 触发器,是一段与某个表相关的sql语句,会在某个时间点,满足某个条件后自动触发执行 其中两个关键因素: 时间点 * 事件发生前,before|事件发生后 after事件 * update delete inser…

PowerDesigner使用技巧

PowerDesigner使用MySQL的auto_increment   ◇问题描述:   PD怎样能使主键id使用MySQL的auto_increment呢? ◇解决方法:    打开table properties窗口 → columns → 选中id列 → 打开columns properties窗口 → 勾选identity即可   …

走进webpack(1)--环境拆分及模块化

初级的文章和demo已经基本完成了,代码也已经上传到了我的github上,如果你对webpack的使用并不是十分了解,那么建议你回头看下走近系列,里面包括了当前项目中使用频繁的插件,loader的讲解。以及基本的webpack配置&#…

适用于微服务架构的Apache Camel

在知道微服务架构被称为之前,我一直在使用它们。 我曾经使用过由隔离模块组成的管道应用程序,这些模块通过队列相互交互。 从那时起,许多(前)ThoughtWorks专家讨论了微服务。 首先是 Fred George, 然后是 J…

题解 P3811 【【模板】乘法逆元】

P3811 【模板】乘法逆元 一个刚学数论的萌新&#xff0c;总结了一下这题的大部分做法 //一、费马小定理快速幂 O(nlogn) 64分 #include<cstdio> using namespace std; typedef long long ll; int a,b; inline ll pow(ll x,ll p) {ll ans1;x%b;while(p) {if (p&1) an…

QueryString加密

有些人不想由URL暴露一些訊息&#xff0c;除了可以使用URL Rewrite之外&#xff0c;其實簡便一點的方法還有使用編碼or加密來達到偽裝的目的。使用Base64的原因是因為他的編碼不會有難以接受的特殊字元(註1)&#xff0c;你也可以用其他的編碼or加密算法替代(註2)。其實這邊已經…