有幻想,有干货,微信搜寻 【大迁世界】 关注这个在凌晨还在刷碗的刷碗智。
本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试残缺考点、材料以及我的系列文章。
背景
在开始之前,先介绍一下咱们目前新我的项目的采纳的技术栈
- 前端公共库:
vue3 + typescript + jsx + antdVue
- 后盾我的项目:
vue3 + typescript + jsx + antdVue
没错,咱们当初都采纳 ts + jsx
语法来开发新我的项目,这里可能会有小伙伴说了,不必 template
吗,装啥装。这外面要探讨内容很多,下次有机会在分享,明天不探讨这个问题。
回到注释~~
这个月老大在 技术优化上 (前端公共库) 派了几个工作给我,其中的一个是“ 路由注册革新,采纳组件内的异步加载 ”,大家一看,必定会想,就这?,这个不是配合 router.beforeEach
和 router.afterEach
在加个显示进度条的库 NProgress
不就完事了嘛。没错,就是按传统的形式会有一些问题,前面会讲,这里咱们先来看传统形式是怎么做的。
传统形式
这个办法大家应该都用过,就是在路由切换的时候,顶部显示一个加载的进度条,咱们这里借助的库是 NProgress
。
第一步,须要装置插件:
yarn add nprogress
第二步,main.ts
中引入插件。
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
第三步,监听路由跳转,进入页面执行插件动画。
路由跳转中
router.beforeEach((to, from, next) => {
// 开启进度条
NProgress.start()
next()})
跳转完结
router.afterEach(() => {
// 敞开进度条
NProgress.done()})
很简略的一个配置,运行后,当咱们切换路由时就会看到顶部有一个进度条了:
这种模式存在两个问题(目前能想到的):
- 弱网络的状况,页面会卡那里,动的很慢
- 当网络断开时,进度条件会始终处于加载的状态,并没有及时反馈加载失败
- 当有比拟非凡需要,如,当加载菜单二时,我想用骨架屏的计划来加载,当加载菜单三,我想要用传统的菊花款式加载,这种状况,咱们当初的计划是很难做的。
弱网络
咱们模仿一下弱网络,关上浏览器控制台,切到 NetWork
,网络换成 Slow 3G,而后在切换路由,上面是我实操的成果:
能够看到,咱们切换到菜单二时,进度条件会缓缓走,页面没有及时切换到菜单二的界面,如果页面内容越多,成果越显著。
网络断开
咱们再来模仿一下网络断开的状况,切到 NetWork
,网络换成 Offline,而后在切换路由,上面是我实操的成果:
会看到在没有网络的状况下,进度条件还是在那始终转,始终加载,没有及时的反馈,体验也是很差的。
咱们想要啥成果
咱们团队想要的成果是
- 只有点击菜单,页面就要切换,即便在弱网的状况
- 在加载失败时要给予一个失败的反馈,而不是让用户傻傻的在那里期待
- 反对每个路由跳转时特有的加载特效
寻找解决方案
为了解决下面的问题,咱们须要一种能异步加载并且能自定义 loading
的办法,查阅了官网文档,Vue2.3 中新增了一个异步组件,容许咱们自定义加载形式,用法如下:
const AsyncComponent = () => ({// 须要加载的组件 (应该是一个 `Promise` 对象)
component: import('./MyComponent.vue'),
// 异步组件加载时应用的组件
loading: LoadingComponent,
// 加载失败时应用的组件
error: ErrorComponent,
// 展现加载时组件的延时工夫。默认值是 200 (毫秒)
delay: 200,
// 如果提供了超时工夫且组件加载也超时了,// 则应用加载失败时应用的组件。默认值是:`Infinity`
timeout: 3000
})
留神如果你心愿在 Vue Router 的路由组件中应用上述语法的话,你必须应用 Vue Router 2.4.0+ 版本。
但咱们当初是应用 Vue3 开发的,所以还得看下 Vue3 有没有相似的办法。查阅了官网文档,也找到了一个办法 defineAsyncComponent
,用法大略如下:
import {defineAsyncComponent} from 'vue'
const AsyncComp = defineAsyncComponent({
// 工厂函数
loader: () => import('./Foo.vue'),
// 加载异步组件时要应用的组件
loadingComponent: LoadingComponent,
// 加载失败时要应用的组件
errorComponent: ErrorComponent,
// 在显示 loadingComponent 之前的提早 | 默认值:200(单位 ms)delay: 200,
// 如果提供了 timeout,并且加载组件的工夫超过了设定值,将显示谬误组件
// 默认值:Infinity(即永不超时,单位 ms)timeout: 3000,
// 定义组件是否可挂起 | 默认值:true
suspensible: false,
/**
*
* @param {*} error 错误信息对象
* @param {*} retry 一个函数,用于批示当 promise 加载器 reject 时,加载器是否应该重试
* @param {*} fail 一个函数,批示加载程序完结退出
* @param {*} attempts 容许的最大重试次数
*/
onError(error, retry, fail, attempts) {if (error.message.match(/fetch/) && attempts <= 3) {
// 申请产生谬误时重试,最多可尝试 3 次
retry()} else {
// 留神,retry/fail 就像 promise 的 resolve/reject 一样:// 必须调用其中一个能力持续错误处理。fail()}
}
})
但在官网 V3 迁徙指南中 官网有指出上面这段话:
Vue Router 反对一个相似的机制来异步加载路由组件,也就是俗称的懒加载。只管相似,这个性能和 Vue 反对的异步组件是不同的。当用 Vue Router 配置路由组件时,你不应该应用
defineAsyncComponent
。你能够在 Vue Router 文档的 懒加载路 由章节浏览更多相干内容。
官网说不应该应用 defineAsyncComponent
来做路由懒加载,但没说不能应用,而咱们当初须要这个办法,所以还是抉择用了(前面遇到坑在分享进去)。
思路
有了下面的办法,咱们当初的思路就是重写 Vue3 中的 createRouter
办法,在 createRouter
咱们递归遍历传进来的 routes
,判断以后的组件是否是异步加载组件,如果是咱们用 defineAsyncComponent
办法给它包装起来。
上面是我当初封装的代码
import {RouteRecordMenu} from '@/components/AdminLayout';
import PageLoading from '@/components/AdminLayout/components/PageLoading';
import PageResult from '@/components/AdminLayout/components/PageResult';
import {
AsyncComponentLoader,
AsyncComponentOptions,
defineAsyncComponent,
h,
} from 'vue';
import {createRouter as vueCreateRouter, RouterOptions} from 'vue-router';
/**
*
* @param routerOptions vue createRouter 的参数
* @param asyncComponentOptions 异步组件配置参数
* @returns
*/
export default function createRouter(
routerOptions: RouterOptions,
{
loadingComponent = PageLoading,
errorComponent = PageResult,
delay = 200,
timeout = 3000,
suspensible = false,
onError,
}: Omit<AsyncComponentOptions, 'loader'> = {},) {const treedRoutes = (childrenRoutes: RouteRecordMenu[]) => {return childrenRoutes.map((childrenRoute: RouteRecordMenu) => {if (childrenRoute.children) {childrenRoute.children = treedRoutes(childrenRoute.children);
} else {if (typeof childrenRoute.component === 'function') {
childrenRoute.component = defineAsyncComponent({
loader: childrenRoute.component as AsyncComponentLoader,
loadingComponent,
errorComponent,
delay,
timeout,
suspensible,
onError,
});
}
}
return childrenRoute;
});
};
treedRoutes(routerOptions.routes);
return vueCreateRouter(routerOptions);
}
下面重写了 createRouter
办法,并提供了可选的配置参数 routerOptions
,routerOptions
外面的字段其实就是 defineAsyncComponent
外面了的参数,除了 loder
。
有了当初的 createRouter
,咱们来看雷同场景,不同成果。
弱网络
能够看到第二种计划在弱计划的状况下,只有咱们切换路由,页面也会马上进行切换,过渡形式也是采纳咱们指定的。不像第一种计划一样,页面会停在点击之前的页面,而后在一下的刷过来。
当切换到菜单时,因为这里我指定的工夫 timeout
为 3
秒,所以在 3
秒内如果没有加载进去,就会显示咱们指定的 errorComponent
。
当初,关上浏览器,切到 NetWork
,网络换成 Offline,也就是断网的状况,咱们在来看下成果。
网络断开
能够看到,当咱们网络断开的时候,在切换页面时,会显示咱们指定 errorComponent
,不像第一种形式一样会始终卡在页面上加载。
变换 Loading
上面来看看,我事例路由:
router.ts
import {RouteRecordRaw, RouterView, createWebHistory} from 'vue-router'
import {RouteRecordMenu} from '@ztjy/antd-vue/es/components/AdminLayout'
import {AdminLayout, Login} from '@ztjy/antd-vue-admin'
import createRouter from './createRoute'
export const routes: RouteRecordMenu[] = [
{
path: '/menu',
name: 'Menu',
component: RouterView,
redirect: '/menu/list',
meta: {
icon: 'fas fa-ad',
title: '菜单一',
},
children: [
{
path: '/menu/list',
component: () => import('@/pages/Menu1'),
meta: {title: '列表',},
},
],
},
{
path: '/menu2',
name: 'Menu2',
component: RouterView,
redirect: '/menu2/list',
meta: {
icon: 'fas fa-ad',
title: '菜单二',
},
children: [
{
path: '/menu2/list',
component: () => import('@/pages/Menu2'),
meta: {title: '列表',},
},
],
},
{
path: '/menu3',
name: 'Menu3',
component: RouterView,
redirect: '/menu3/list',
meta: {
icon: 'fas fa-ad',
title: '菜单三',
},
children: [
{
path: '/menu3/list',
component: () => import('@/pages/Menu3'),
meta: {title: '列表',},
},
],
},
]
const router = createRouter({history: createWebHistory('/'),
routes: [
{
path: '/login',
component: Login,
props: {title: '商化前端后盾登录',},
},
{
path: '/',
redirect: '/menu',
component: AdminLayout,
props: {
title: '商化前端 后盾 模板',
routes,
},
meta: {title: '首页',},
children: routes as RouteRecordRaw[],},
],
})
export default router
咱们当初想用上面曾经封装好的冒泡加载形式来代替菊花的款式:
很简略,咱们只须要把对应加载组件 (BubbleLoading) 的名称,传给 createRouter
既可,为了演示成果,咱们把网络切花到 Slow 3G,代码如下:
router.ts
/*** 这里省略很多字 **/
const router = createRouter(
{history: createWebHistory('/'),
routes: [/*** 这里省略很多字 **/]
},
{loadingComponent: BubbleLoading, // 看这里看这里}
)
export default router
花里胡哨
如果咱们只有点击菜单二才用 BubbleLoading,点击其它的就用菊花的加载,那又要怎么做呢?
这里,大家如果认真看下面二次封装的 createRouter
办法,可能就晓得怎么做了,其中外面有一个判断就是
typeof childrenRoute.component === 'function'
其实我做的就是判断如果里面传进来的路由采纳的异步加载的形式,我才对用 defineAsyncComponent
重写,其它的加载形式我是不论的,所以,咱们想要自定义各自的加载形式,只有用 defineAsyncComponent
重写即可。
回到咱们的 router.ts 代码,
// 这里省略一些代码
export const routes: RouteRecordMenu[] = [
// 这里省略一些代码
{
path: '/menu2',
name: 'Menu2',
component: RouterView,
redirect: '/menu2/list',
meta: {
icon: 'fas fa-ad',
title: '菜单二',
},
children: [
{
path: '/menu2/list',
component: defineAsyncComponent({ // 看这里
loader: () => import('@/pages/Menu2'),// 看这里
loadingComponent: BubbleLoading,// 看这里
}),
meta: {title: '列表',},
},
],
},
// 这里省略一些代码
]
// 这里省略一些代码
在下面,咱们用 defineAsyncComponent
定义菜单二的 component
加载形式,运行成果如下:
从图片能够看出点击菜单一和三时,咱们应用菊花的加载形式,点击菜单二就会显示咱们自定义的加载形式。
留神
这里有一个显性的 bug,就是上面代码:
component: defineAsyncComponent({loader: () => import('@/pages/Menu2'),
loadingComponent: BubbleLoading,
}),
不能用函数的形式来写,如下所示:
component: () => defineAsyncComponent({loader: () => import('@/pages/Menu2'),
loadingComponent: BubbleLoading,
}),
这里因为我在 createRouter 办法中应用 typeof childrenRoute.component === 'function'
来判断,所以下面代码又会被 defineAsyncComponent
包起来,变成两层的defineAsyncComponent
,所以页面加载会出错。
我也想解决这个问题,但查了很多材料,没有找到如何在办法中,判断办法采纳的是 defineAsyncComponent 形式,即上面这种模式:
component: () => defineAsyncComponent({loader: () => import('@/pages/Menu2'),
loadingComponent: BubbleLoading,
}),
如果有小伙伴晓得的,能够私信通知我一下。
本文到这里就分享完了,我是刷碗智,我要去做饭了,咱们下期见~
代码部署后可能存在的 BUG 没法实时晓得,预先为了解决这些 BUG,花了大量的工夫进行 log 调试,这边顺便给大家举荐一个好用的 BUG 监控工具 Fundebug。
交换
文章每周继续更新,能够微信搜寻「大迁世界」第一工夫浏览和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 曾经收录,整顿了很多我的文档,欢送 Star 和欠缺,大家面试能够参照考点温习,另外关注公众号,后盾回复 福利,即可看到福利,你懂的。