Vue Router 学习笔记-扩展

1/29/2023 Vue

[TOC]

# Vue Router 学习笔记-扩展

这部分记录了VueRouter的扩展部分,覆盖了路由的众多场景。

# 路由名匹配

# 组件名匹配

根据name或path来匹配

const routes = [
  { path: '/', component: Home },
  { name: 'about', path: '/about', component: About },
]

# 动态路由匹配

为了将 /users/johnny/users/jolyne 都匹配到 User页面,引入动态路由匹配的概念,用到了params参数。

注意:**导航函数如果传入了path,则会忽略params。**因为path本身就是路径,params是用于动态路由中组成path的一部分(例如path: "/a/:userId"中的userId这个动态参数),所以path和params不能互相传。(params可通过this.$route.params获取)

const routes = [
  // 动态字段以冒号开始,id需要做为 params 
  { name: "PageA", path: '/users/:id', component: User },
]

// 注意 params 不能与 path一起使用
router.push({
          name: "PageA",
          params: {
            id: 5,
          }
        });

# 路由正则匹配

为了适应更多复杂的场景,引入了正则的写法,具体参考:路由的匹配语法 (opens new window)

# 嵌套路由

解决一个组件下引入不同组件的写法,核心是 children 的写法。

例如下面的嵌套组件中,在User下需要嵌套 Profile 和 Posts,

/user/johnny/profile                  /user/johnny/posts
+------------------+                  +-----------------+
| User             |                  | User            |
| +--------------+ |                  | +-------------+ |
| | Profile      | |  +------------>  | | Posts       | |
| |              | |                  | |             | |
| +--------------+ |                  | +-------------+ |
+------------------+                  +-----------------+
// User组件中使用 <router-view>
const User = {
  template: `
    <div class="user">
      <h2>User {{ $route.params.id }}</h2>
      <router-view></router-view>
    </div>
  `,
}

// 在 User的路由配置中,使用 children,是一个数组,单个结构同路由的写法
const routes = [
  {
    path: '/user/:id',
    component: User,
    children: [
      {
        // 当 /user/:id/profile 匹配成功
        // UserProfile 将被渲染到 User 的 <router-view> 内部
        path: 'profile',
        component: UserProfile,
      },
      {
        // 当 /user/:id/posts 匹配成功
        // UserPosts 将被渲染到 User 的 <router-view> 内部
        path: 'posts',
        component: UserPosts,
      },
    ],
  },
]

# 命名路由

为组件设置 name 属性,后续即可使用这个name进行路由跳转。

为组件设置 name 属性,设置后可以使用 params属性,即 params 必须与name配套使用,且params是自动解码和编码的

# 命名视图

当同级需要展示多个视图而不是嵌套的时候,需要使用命名视图。

命名视图的核心是components配置。

// 页面有 sidebar 和 main 两个视图,注意如果 router-view 没有设置名字,那么默认为 default
<router-view class="view left-sidebar" name="LeftSidebar"></router-view>
<router-view class="view main-content"></router-view>
<router-view class="view right-sidebar" name="RightSidebar"></router-view>

// 路由配置中使用 components
const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    {
      path: '/',
      components: {
        default: Home,
        // LeftSidebar: LeftSidebar 的缩写
        LeftSidebar,
        // 它们与 `<router-view>` 上的 `name` 属性匹配
        RightSidebar,
      },
    },
  ],
})

# 重定向和别名

在路由配置中,可以设置重定向和别名。

重定向使用redirect

// 重定向,redirect 可使用字符串、命名路由、动态函数
// 从 /home 重定向到 /
const routes = [{ path: '/home', redirect: '/' }]
const routes = [{ path: '/home', redirect: { name: 'homepage' } }]
const routes = [
  {
    // /search/screens -> /search?q=screens
    path: '/search/:searchText',
    redirect: to => {
      // 方法接收目标路由作为参数
      // return 重定向的字符串路径/路径对象
      return { path: '/search', query: { q: to.params.searchText } }
    },
  },
  {
    path: '/search',
    // ...
  },
]


可以给组件起个别名,别名使用alias

//  / 的别名是 /home,访问 /home 与 / 一样
const routes = [{ path: '/', component: Homepage, alias: '/home' }]

# 路由组件传参

参数除了可以通过 $route.params 传递,还可以把 params的参数都设置为 props,即在使用params时,多了一个设置props的选项。

关键字为:props

把params设置为props有三种模式:

  1. 布尔模式,设置 props: true ,直接将route.params 将设置为 组件的props
  2. 对象模式,设置props为Object,适合props为静态值的情况,注意当使用此模式时,组件内props会忽略params参数
  3. 函数模式,设置 props 为函数,适合将params值与静态值结合的情况

// 布尔模式
 {
    path: "/b/:id",
    name: "PageB",
    component: () => import("@/page/PageB.vue"),
    props: true,
  }

// 对象模式
  {
    path: "/b/:id",
    name: "PageB",
    component: () => import("@/page/PageB.vue"),
    props: {
      objectModel: 123,
    },
  }

// 函数模式
const routes = [
  {
    path: '/search',
    component: SearchUser,
    props: route => ({ query: route.query.q })
  }
]

# 历史记录模式

单页面应用SPA,当需要做到改变路由,但是不请求后端时,基于浏览器的特性有两种实现方式

  • Hash 模式,基于浏览器不会将#后面的发送到后端的特性(即url中带 #,例如 baidu.com/#/abc,该链接后#后面的不会发送到后端),当#后面改变时,监听 hashchange 事件,即可根据改变的url找到对应的组件。
  • H5模式,是基于H5新增的两个方法:pushState 和 replaceState,使用这两个方法改变浏览器历史堆栈,不会触发后端请求。但是如果用户手动刷新,就会匹配完整url,因此需要后端服务设置 重定向

两个模式的区别:

  1. hash模式中url一定会带#,H5模式不会是标准的url,当url众不允许出现 # 时,就需要使用 H5模式
  2. h5模式下,当用户刷新页面,会将整个url发送到后端,会出现404,需要后端做对应重定向配置

具体参考:Vue-router 中hash模式和history模式的区别 (opens new window)

# 路由元信息

字段meta,用于将 任意附加信息 附加到路由上,如权限控制等,可通过 $route.meta 获取到所有meta信息。

// 路由配置内设置 meta 信息,可添加任意信息,代码内可通过 $route.meta获取,守卫内可通过to.meta 或  from.meta获取
const routes = [
  {
    path: '/posts',
    component: PostsLayout,
    children: [
      {
        path: 'new',
        component: PostsNew,
        // 只有经过身份验证的用户才能创建帖子
        meta: { requiresAuth: true }
      },
      {
        path: ':id',
        component: PostsDetail
        // 任何人都可以阅读文章
        meta: { requiresAuth: false }
      }
    ]
  }
]

# 数据获取

组件也可以在导航守卫函数内获取数据,然后使用next函数进行跳转

# 过渡动效

过渡动效 (opens new window)

# 滚动行为

当组件切换时,可使用 scrollBehavior 设置组件切换后 页面滚动的位置。scrollBehavior 函数返回值类似 Window.scroll() 函数。

const router = createRouter({
  history: createWebHashHistory(),
  routes: [...],
	// scrollBehavior 函数返回值类似 Window.scroll() 函数
  scrollBehavior (to, from, savedPosition) {
    // return 期望滚动到哪个的位置
		
		// 始终滚动到顶部
    return { top: 0 }
		
		 // 始终在元素 #main 上方滚动 10px
    return {
      // 也可以这么写
      // el: document.getElementById('main'),
      el: '#main',
      top: -10,
    }

		// 模拟 “滚动到锚点” 的行为
      return {
        el: to.hash,
      }

		// 支持 behavior 参数
      return {
        el: to.hash,
        behavior: 'smooth',
      }

		// 延迟滚动,需要返回 promise函数
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve({ left: 0, top: 0 })
      }, 500)
    })
  }
})

# 路由懒加载

Vue Router 支持开箱即用的动态导入 (opens new window),尽量使用动态导入

// 静态导入
import UserDetails from './views/UserDetails.vue'
const router = createRouter({
  // ...
  routes: [{ path: '/users/:id', component: UserDetails }],
})

// 动态导入
const router = createRouter({
  // ...
  routes: [{ path: '/users/:id', component: () => import('./views/UserDetails.vue' }],
})
           
// component 也支持配置 promise的函数,可进行更复杂的配置
const UserDetails = () =>
  Promise.resolve({
    /* 组件定义 */
  })

# 把组件按组分块

webpack,需要 Webpack > 2.4,将多个组件打包到同个异步块(chunk)内,使用:webpackChunkName

const UserDetails = () =>
  import(/* webpackChunkName: "group-user" */ './UserDetails.vue')
const UserDashboard = () =>
  import(/* webpackChunkName: "group-user" */ './UserDashboard.vue')
const UserProfileEdit = () =>
  import(/* webpackChunkName: "group-user" */ './UserProfileEdit.vue')

Vite

在vite.config.js文件中使用:rollupOptions

// vite.config.js
export default defineConfig({
  build: {
    rollupOptions: {
      // https://rollupjs.org/guide/en/#outputmanualchunks
      output: {
        manualChunks: {
          'group-user': [
            './src/UserDetails',
            './src/UserDashboard',
            './src/UserProfileEdit',
          ],
        },
    },
  },
})

可以封装 router-link 形成自定义的 RouterLink,一般用于 需要给RouterLink添加更多额外的props

// 使用 AppLink 封装  RouterLink
<template>
  <AppLink
    v-bind="$attrs"
    class="inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 focus:outline-none transition duration-150 ease-in-out hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out"
    active-class="border-indigo-500 text-gray-900 focus:border-indigo-700"
    inactive-class="text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:text-gray-700 focus:border-gray-300"
  >
    <slot />
  </AppLink>
</template>


// AppLink
import { RouterLink, useLink } from 'vue-router'

export default {
  name: 'AppLink',

  props: {
    // 如果使用 TypeScript,请添加 @ts-ignore
    ...RouterLink.props,
    inactiveClass: String,
  },

  setup(props) {
    // `props` 包含 `to` 和任何其他可以传递给 <router-link> 的 prop
    const { navigate, href, route, isActive, isExactActive } = useLink(props)

    // profit!

    return { isExternalLink }
  },
}

# 导航故障

导航函数是异步的,当触发导航行为时,会有以下情况可能导致用户停留在当前页面:

此时需要检测导航故障。

导航函数返回的promise对象参数为 NavigationFailure 时,说明被导航守卫阻拦,即发生导航故障。并可对NavigationFailure参数调用 isNavigationFailure 判断具体类型(NavigationFailureType)。有三种具体类型

  • aborted:在导航守卫中返回 false 中断了本次导航。
  • cancelled: 在当前导航还没有完成之前又有了一个新的导航。比如,在等待导航守卫的过程中又调用了 router.push
  • duplicated:导航被阻止,因为我们已经在目标位置了。
const navigationResult = await router.push('/my-profile')
if (navigationResult) {
  // 导航被阻止,发生导航故障
  // 使用 isNavigationFailure 判断错误类型,注意如果忽略第二个类型错误检察,则只会判断 navigationResult 是否是 NavigationFailureType
  if (isNavigationFailure(navigationResult, NavigationFailureType.aborted)) {
    // 当为 NavigationFailureType.aborted 时
    // 给用户显示一个小通知
    showToast('You have unsaved changes, discard and leave anyway?')
  }else{
    // 错误不是 NavigationFailureType.aborted 类型时
  }
} else {
  // 导航成功 (包括重新导航的情况)
}


// 检测重定向
await router.push('/my-profile')
if (router.currentRoute.value.redirectedFrom) {
  // redirectedFrom 是解析出的路由地址,就像导航守卫中的 to和 from
}

# 动态路由

路由配置一般通过router.js文件进行静态配置,但是VueRouter提供了CLI接口,可以动态的添加或删除路由。主要通过router.addRoute()router.removeRoute()实现。函数参数为路由对象,即同配置内写法一样。

其他:

  • 在导航守卫中添加路由,可以在导航守卫中添加路由,添加完触发重定向即可

  • 删除路由方式:

    • 添加一个名称相同的路由,之前的同名路由会被删除

    • 通过调用 router.addRoute() 返回的回调函数

      const removeRoute = router.addRoute(routeRecord)
      removeRoute() // 删除路由如果存在的话
      
    • 通过使用 router.removeRoute() 按名称删除路由

      router.addRoute({ path: '/about', name: 'about', component: About })
      // 删除路由
      router.removeRoute('about')
      
  • 添加嵌套路由,addRoute的第一个参数传入 name

    router.addRoute({ name: 'admin', path: '/admin', component: Admin })
    // 为 admin 添加嵌套路由
    router.addRoute('admin', { path: 'settings', component: AdminSettings })
    
    // 等效于
    router.addRoute({
      name: 'admin',
      path: '/admin',
      component: Admin,
      children: [{ path: 'settings', component: AdminSettings }],
    })
    
  • 查看现有路由:

    • router.hasRoute(),检查路由是否存在
    • router.getRoutes(),获取一个包含所有路由记录的数组。
Last Updated: 1/28/2023, 6:19:34 PM