十四、使用 Vue Router 开发单页应用(6)

本章概要

  • 延迟加载路由
  • 等待导航结果
  • 动态路由
    • 添加路由
    • 在导航守卫中添加路由
    • 删除路由
    • 添加嵌套路由
    • 查看现有路由

14.14 延迟加载路由

当应用变得复杂后,路由组件也会增多,而 Webpack 的打包机制会将应用程序中所有的 JavaScript 打包成一个文件(除 public 目录下的 js 文件),这个文件可能相当大,影响页面的加载效率。如果可以将每个路由的组件分割成单独的块,并且只在访问路由时加载它们,那么效率会更高。

Vue Router 本身支持动态导入,因此可以将静态导入替换为动态导入。如下:

// 将
// import UserDetails from './views/UserDetails'
// 替换为
const UserDetails = () => import('./views/UserDetails')

const router = createRouter({
  // ...
  routes:[{
    path:'/users/:id',
    component:UserDetails
  }]
})

component 和 component 选项接收一个返回组件 Promise 的函数,Vue Router 只在第一次进入页面时获取它,然后使用缓存版本。这意味着也可以拥有更复杂的函数,只要它们返回一个 Promise 。如下:

const UserDetails = () => 
  Promise.resolve({
    // 组件定义
  })

通常,对所有路由始终使用动态导入是一个很好的抉择。
需要注意的是,不要对路由组件使用异步组件,异步组件仍然可以在路由组件内部使用,但是路由组件本身只是动态导入。
当使用像 Webpack 这样的打包器时,可以自动受益于其代码拆分功能,在代码中所有被 import() 的组件,都将打包成一个单独的 JS 文件,在路由匹配到该组件时,就会自动请求这个资源,实现异步加载。
如果希望将嵌套在某个路由下的所有组件都打包到一同一个异步块(chunk)中,那么只需要使用特殊的注释语法提供一个块名来使用命名块(需要 webpack>2.4)。如下:

{
  path:'/news',
  name:'news',
  component:() => import(/* webpackChunkName:"home" */ '@/components/News'),
  meta:{
    title:'新闻'
  }
}{
  path:'/books',
  name:'books',
  component:() => import(/* webpackChunkName:"home" */ '@/components/Books'),
  meta:{
    title:'图书列表'
  }
},
{
  path:'/videos',
  name:'videos',
  component:() => import(/* webpackChunkName:"home" */ '@/components/Videos'),
  meta:{
    title:'视频'
  }
},

在构建发布版本时,就会将 News,Books、Videos 组件打包到同一个名字中包含 home 的 JS 文件中。

14.15 等待导航结果

当使用 router-link 时,Vue Router 调用 router.push() 函数触发导航,虽然大多数链接的预期行为是将用户导航到新页面,但也有一些情况,用户将保持在同一页面:

  • 用户已经在他们试图导航到的页面上
  • 导航守卫通过返回 false 终止导航
  • 一个新的导航守卫在前一个还未完成时发生
  • 导航守卫通过返回一个新的位置(例如,返回’/login’)来重定向到其它地方
  • 导航守卫抛出错误

如果想在导航完成后做些事,那么需要有一种方式可以在调用 router.push() 函数后进行等待。假设有一个导航菜单,经由它可以跳转到不同的页面,但我们想在导航到页面后隐藏这个菜单,于是编写如下代码:

router.push('/my-profile')
this.isMenuOpen = false

但是这将立即关闭菜单,因为导航是异步的,需要等待 router.push() 返回的 Promise 。如下:

await router.push('/my-profile')
this.isMenuOpen = false

现在,菜单将在导航完成后关闭,但如果导航被阻止,它也将关闭,我们需要一种方法检测是否真的改变了页面。

如果导航被阻止,导致用户停留在同一页面上,则 router.push() 返回的 Promise 的已解析(resolved)值将是一个导航失败(navigation failure),否则,它将是一个计算为 false 的值(通常是 undifined),这样我们就能区分导航是否成功、页面是否被改变。如下:

const navigationResult = await router.push('my-profile')
if (navigationResult) {
  // 导航被阻止
}else{
  // 导航成功(包括重定向的情况)
  this.isMenuOpen = false
}

导航失败是带有一些额外属性的 Error 实例,这些属性为我们提供了足够的信息,让我们知道哪个导航被阻止了,以及被阻止的原因。要检查一个导航结果的性质,可以使用 isNavigationFailure() 函数。如下:

import { navigationFailureType,isNavigationFailure } from 'vue-router'
const failure = await router.push('/articles/2')

// 如果用户没有保存就离开了文章的编辑页
if (isNavigationFailure(failure,NavigationFailureType.aborted)){
  // 向用户显示一个通知
  showToast('You have unsaved changes,discard and leave anyway?')
}

NavigationFailureType 是枚举类,共有 3 个枚举值,他们的含义如下:

  • aborted:中止的导航是由于导航守卫返回 false 或调用 next(false) 函数而失败的导航
  • cancelled:在当前导航完成之前发生了新的导航。例如,在一个导航守卫内部等待时调用了 router.push() 函数
  • duplicated:导航被阻止,因为已经在目标位置

如果省略第二个参数,即调用 isNavigationFailure(failure) 函数,那么该函数只检查失败是否是导航失败。
如果所有导航失败都暴露在 to 和 from 属性中,以反映失败导航的当前位置和目标位置。如下:

// 视图访问 admin 页面
router.push('/admin').catch(failure => {
  if(isNavigationFailure(failure,NavigationFailureType.aborted)){
    failure.to.path  //'/admin'
    failure.from.path  // '/'
  }
})

当在导航守卫内部返回一个新位置时,将触发一个新的导航,该导航将覆盖正在进行的导航。与其它返回值不同,重定向不会阻止导航,而是创建一个新的导航。可以通过读取路由位置对象的 redirectedFrom 属性进行检测。如下:

await router.push('/my-profile')
if (router.currentRoute.value.redirectedFrom){
  // redirectedFrom 在导航守卫中被解析为路由位置对象,如同 to 和 from
}

14.16 动态路由

向路由器添加路由通常是通过 routes 选项完成的,但是在某些情况下,可能希望在应用程序已经运行时添加或删除路由,也就是以变成的方式添加或删除路由。

14.16.1 添加路由

动态路由主要通过两个方法来实现:router.addRoute() 和 router.removeRoute() 。router.addRoute() 方法只是注册一个新的路由,如果新注册的路由与当前位置匹配,则需要使用 router.push() 函数或 router.replace() 函数手动导航以显示新的路由。

在下面的代码中,只定义了一个单一的路由:

const router = createRouter({
  history:createWebHistory(),
  routes:[{ path:'/:articleName',component:Article }]
})

传到任何页面,如 /about 、 /store ,都将渲染 Article 组件。如果在 /about 上想显示一个新路由,那么仅编写下面的代码是不够的:

router.addRoute({ path:'/about',component:About })

路由到 /about 页面,仍将显示 Atricle 组件。要显示 About 组件,需要手动调用 router.replace() 函数改变当前位置并覆盖之前的位置。如下:

router.addRoute({ path:'/about',component:About })
// 也可以使用 this.$route 或 route = useRoute() (在 setup() 函数中)
router.replace(router.currentRoute.value.fullPath)

如果需要等待新路由显示,则可以调用 await router.replace()

14.16.2 在导航守卫中添加路由

在导航守卫中添加或删除路由,不要调用 router.replace() 函数,而是通过返回新的位置来触发重定向。如下:

router.beforeEach( to => {
  if (!hasNecessaryRoute(to)){
    router.addRoute(generateRoute(to))
    //触发重定向
    return to.fullPaht
  }
})

上面示例假设两件事:首先,新添加的路由记录将匹配到目标位置,这实际上导致了与我们尝试访问的位置不同;其次,hasNecessaryRoute() 函数在添加新路由后返回 false ,以避免无限重定向。
因为进行了重定向,所以替换了正在进行的导航,其行为与前面的示例类似。在实际场景中,添加更有可能发生在导航守卫之外。例如,当一个视图组件挂载时,它会注册新的路由。

14.16.3 删除路由

有几种不同的方式可以删除现有的路由。
(1)通过添加名称冲突的路由。如果添加了一个与现有路由同名的路由,那么会先删除该路由,然后再添加路由。如下:

router.addRoute({ path:'/about',name:'about',component:About })
// 这将先删除添加的路由,因为他们具有相同的名称且名称是唯一的
router.addRoute({ path:'/other',name:'about',component:Other })

(2)通过调用 router.addRoute() 函数返回的回调。如下:

const removeRoute = router.addRoute(routeRecord)
removeRoute() //如果路由存在,则删除它

这在路由没有名称时非常有用。
(3)通过调用 router.removeRoute() 函数按名称删除一个路由。如下:

router.addRoute({ path:'/about',name:'about',component:About })
// 删除路由
router.removeRoute('about')

注意:
如果希望使用 removeRoute() 函数,但有希望避免名称冲突,可以在路由中使用 Symbol 作为名称。

当一个路由被删除时,它的所有别名和子路由都会被删除。

14.16.4 添加嵌套路由

要向现有路由添加嵌套路由,可以将路由的名称作为第一个参数传递给 router.addRoute() 函数,这将有效地添加路由,就像通过 children 添加一样。如下:

router.addRoute({ path:'/admin',name:'admin',component:Admin })
router.addRoute('admin', { path:'settings',component:AdminSettings })

这相当于:

router.addRoute({
  path:'/admin',
  name:'admin',
  component:Admin,
  children:[{ path:'settings',component:AdminSettings }]
})

14.16.5 查看现有路由

Vue Router 给出了两个查看现有路由的函数:

  • router.hasRoute() 检查路由是否存在
  • router.getRoutes() 获取包含所有路由记录的数组
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只小熊猫呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值