背??景在开始之前先介绍一下我们新项目使用的技术栈的前端公共库:vue3+typescript+jsx+antdVue后台项目:vue3+typescript+jsx+antdVue是的,我们都用ts+jsx语法开发新项目,这里可能有朋友会说,你要不要模板啊,安装什么。这里有很多值得讨论的地方,下次有机会再分享。我今天不讨论这个问题。回到正文~~这个月老大在技术优化(前端公库)方面给我发了几个任务,其中一个就是“路由注册改造,在组件中使用异步加载”,大家肯定会想,就是这样?这不就是加了一个库NProgress来配合router.beforeEach和router.afterEach显示进度条吗?没错,传统的方式存在一些问题,后面会讲到,这里我们先看看传统的方式是怎么做的。传统的方法大家应该都用过,就是在切换路由的时候,在最上面显示一个加载进度条。我们在这里使用的库是NProgress。第一步安装插件:yarnaddnprogress第二步将插件引入main.ts。importNProgressfrom'nprogress'import'nprogress/nprogress.css'第三步监听路由跳转,进入页面执行插件动画。Router.beforeEach((to,from,next)=>{//打开进度条NProgress.start()next()})跳转结束router.afterEach(()=>{//关闭进度条NProgress.done()})是一个非常简单的配置。运行之后,当我们切换路由的时候,会在最上面看到一个进度条:这个模式有两个问题(目前可以想象):弱网情况,页面会卡在那里,移动很慢。断网时,进度条会一直处于加载状态,加载失败没有及时反馈。当有特殊需求的时候,比如加载菜单2的时候,我想用骨架屏的方案加载,加载菜单3的时候,我想用传统的菊花样式加载,这样的话,我们现在的方案就很难了制成。弱网我们来模拟一个弱网,打开浏览器控制台,切换到NetWork,把网络改成**Slow3G**,然后切换路由,下面是我实际操作的效果:如你所见,我们切换到菜单2,进度条会变慢,页面没有及时切换到菜单2的界面。页面内容越多,效果会越明显。网络断开。我们再模拟一下断网的情况。切换到NetWork,把网络改成**Offline**,然后切换路由。下面是我实际运行的效果:你会看到在没有网络的情况下一直在运行和加载,没有及时反馈,体验也很差。Whateffectdowewant我们团队想要的效果是:只要点击菜单,页面就会切换,即使在弱网的情况下,加载失败也应该给出失败反馈,而不是让用户等待支持愚蠢地针对每条路线跳转时独特的加载效果寻找解决方案。为了解决以上问题,我们需要一个可以异步加载和自定义加载的方法。查阅了官方文档,在Vue2.3中增加了一个异步组件。允许我们自定义加载方式,用法如下:constAsyncComponent=()=>({//需要加载的组件(应该是一个`Promise`对象)component:import('./MyComponent.vue'),//异步组件加载时使用的组件loading:LoadingComponent,//加载失败时使用的组件error:ErrorComponent,//显示组件加载时的延迟时间默认值为200(毫秒)delay:200,//如果提供了超时时间并且组件加载也超时了,//使用加载失败时使用的组件默认值为:`Infinity`timeout:3000})注意如果要使用上面的VueRouter路由组件中的语法,必须使用VueRouter2.4.0+版本。但是我们现在是用Vue3开发,所以得看看Vue3中有没有类似的方法。查阅官方文档后,还发现了一个方法defineAsyncComponent,大致使用如下:vue'),//加载异步组件时使用的组件loadingComponent:LoadingComponent,//加载失败时使用的组件errorComponent:ErrorComponent,//显示loadingComponent前的延迟|默认值:200(单位ms)delay:200,//如果设置了timeout,加载组件的时间超过了设置的值,就会显示错误的组件//默认值:Infinity(即永不超时,单位ms)timeout:3000,//定义组件是否可以挂起|默认值:truesuspensible:false,/****@param{*}error错误消息对象*@param{*}retry一个函数,用于指示加载器是否应该在promise时重试loader拒绝*@param{*}fail指示loader的函数完成退出*@param{*}attemptsMaximumnumberofretriesallowed*/onError(error,retry,fail,attempts){if(error.message.match(/fetch/)&&attempts<=3){//重试时请求出现错误,最多尝试3次retry()}else{//注意retry/fail就像resolve/rejectofpromise://必须调用其中一个才能继续错误处理.fail()}}})但是在官方的V3迁移指南中,官方指出了如下一段话:VueRouter支持类似的机制来异步加载路由组件,也就是俗称的懒加载。虽然类似,但这个功能与Vue支持的异步组件不同。使用VueRouter配置路由组件时,不应使用defineAsyncComponent。您可以在VueRouter文档的惰性路由章节中阅读更多相关信息。官网说了defineAsyncComponent不应该用于路由的懒加载,但是并没有说不能用,而我们现在需要这个方法,所以还是选择使用(遇到了会分享以后坑)。思路有了上面的方法,我们目前的思路是重写Vue3中的createRouter方法。在createRouter中,我们递归遍历传入的路由,判断当前组件是否为异步加载组件。如果是这样,我们使用defineAsyncComponent方法将其包装起来。下面是我现在封装的代码:import{RouteRecordMenu}from'@/components/AdminLayout';importPageLoadingfrom'@/components/AdminLayout/components/PageLoading';importPageResultfrom'@/components/AdminLayout/components/PageResult';import{AsyncComponentLoader,AsyncComponentOptions,defineAsyncComponent,h,}from'vue';import{createRouterasvueCreateRouter,RouterOptions}from'vue-router';/****@paramrouterOptionsvuecreateRouter的参数*@paramasyncComponentOptions异常组配置参数*@returns*/exportdefaultfunctioncreateoutRouter(:RouterOptions,{loadingComponent=PageLoading,errorComponent=PageResult,delay=200,timeout=3000,suspensible=false,onError,}:Omit={},){consttreedRoutes=(childrenRoutes:RouteRecordMenu[])=>{returnchildrenRoutes.map((childrenRoute:RouteRecordMenu)=>{if(childrenRoute.children){childrenRoute.children=treedRoutes(childrenRoute.children);}else{if(typeofchildrenRoute.component==='function'){childrenRoute.component=defineAsyncComponent({loader:childrenRoute.componentasAsyncComponentLoader,loadingComponent,errorComponent,delay,timeout,suspensible,onError,});}}returnchildrenRoute;});};treedRoutes(routerOptions.routes);returnvueCreateRouter(routerOptions);}重写在createRouter之上方法,并提供了可选的配置参数routerOptions,routerOptions中的字段其实就是defineAsyncComponent中的参数,只不过loder有当前的createRouter,我们来看同一个场景,不同的效果。对于弱网,我们可以看到第二种方案是在弱网的情况下,只要我们切换路由,页面就会立刻切换,过渡方式也是我们指定的。与第一种方案不同的是,页面会停在点击前的页面,然后点击一下就滑过去。切换到菜单时,因为我这里指定的超时时间是3秒,如果3秒内没有加载,就会显示我们指定的errorComponent。现在,打开浏览器,切换到NetWork,把网络改成**Offline**,也就是网络断开的时候,我们来看看效果。当网络断开时,我们可以看到,当我们的网络断开时,当我们切换页面时,会显示指定的errorComponent,不像第一种方法,会一直卡在页面加载中。更改Loading看下面,我的示例路由:router.tsimport{RouteRecordRaw,RouterView,createWebHistory}from'vue-router'import{RouteRecordMenu}from'@ztjy/antd-vue/es/components/AdminLayout'import{AdminLayout,Login}from'@ztjy/antd-vue-admin'importcreateRouterfrom'./createRoute'exportconstroutes:RouteRecordMenu[]=[{path:'/menu',name:'Menu',component:RouterView,redirect:'/menu/list',meta:{icon:'fasfa-ad',title:'菜单1',},children:[{path:'/menu/list',component:()=>import('@/pages/Menu1'),meta:{title:'list',},},],},{path:'/menu2',name:'Menu2',component:RouterView,redirect:'/menu2/list',meta:{图标:'fasfa-ad',title:'Menu2',},children:[{path:'/menu2/list',component:()=>import('@/pages/Menu2'),meta:{title:'List',},},],},{path:'/menu3',name:'Menu3',component:RouterView,redirect:'/menu3/list',meta:{icon:'fasfa-ad',title:'Menu3',},children:[{path:'/menu3/list',component:()=>import('@/pages/Menu3'),meta:{title:'list',},},],},]constrouter=createRouter({history:createWebHistory('/'),routes:[{path:'/login',component:Login,props:{title:'商业前端后台登录',},},{path:'/',redirect:'/menu',component:AdminLayout,props:{title:'商业前端后台模板',routes,},meta:{title:'首页',},children:routesasRouteRecordRaw[],},],})exportdefaultrouter我们现在要使用封装好的下面一种替换菊花样式的气泡加载方法:很简单,我们只需要将对应的加载组件(BubbleLoading)的名称传递给createRouter即可,为了演示效果,我们将网络切到慢3G,代码为如下:router.ts/***这里省略很多字**/constrouter=createRouter({history:createWebHistory('/'),routes:[/***这里省略很多字**/]},{loadingComponent:BubbleLoading,//看这里})exportdefaultrouter很花哨,如果我们只需要点击菜单2就可以使用BubbleLoading,点击其他就可以使用菊花加载,那怎么办呢?这里,如果你仔细看上面二级包的createRouter方法,你可能就知道怎么做了,其中一个判断就是typeofchildrenRoute.component==='function'其实我做的是判断if外面传入的路由采用异步加载的方式,我会用defineAsyncComponent重写,其他的加载方式我不管,所以我们要自定义自己的加载方式,用defineAsyncComponent重写就可以了回到我们的router.ts代码,//这里省略了一些代码exportconstroutes:RouteRecordMenu[]=[//这里省略了一些代码{path:'/menu2',name:'Menu2',component:RouterView,redirect:'/menu2/list',meta:{icon:'fasfa-ad',title:'Menu2',},children:[{path:'/menu2/list',component:defineAsyncComponent({//看这里loader:()=>import('@/pages/Menu2'),//看这里loadingComponent:BubbleLoading,//看这里}),meta:{title:'List',},},],},//这里省略部分代码]//此处省略部分代码,我们使用defineAsyncComponent定义菜单2的组件加载方式,运行效果如下:从图中可以看出,在点击菜单1和3时,我们使用菊花加载方式,点击菜单2,会显示我们自定义的加载方式。注意这里有一个明显的bug,就是下面的代码:component:defineAsyncComponent({loader:()=>import('@/pages/Menu2'),loadingComponent:BubbleLoading,}),不能写成这样的形式一个函数,如下图:component:()=>defineAsyncComponent({loader:()=>import('@/pages/Menu2'),loadingComponent:BubbleLoading,}),这里因为我使用了typeofchildrenRoute.component=在createRouter方法中=='function'来判断,所以上面的代码会再次被defineAsyncComponent包裹,变成一个二层的defineAsyncComponent,所以页面会加载错误。我也想解决这个问题,但是查了很多资料,都没有找到方法中defineAsyncComponent方法的使用方法,也就是下面的形式:component:()=>defineAsyncComponent({loader:()=>import('@/pages/Menu2'),loadingComponent:BubbleLoading,}),本篇分享到此结束,我是玩玩志,下期见~