vue-router查漏补缺

时间:2024-12-12 20:21:39

一、动态路由匹配

1.带参数的动态路由匹配
import User from './User.vue'

// 这些都会传递给 `createRouter`
const routes = [
  // 动态字段以冒号开始
  { path: '/users/:efg', component: User },
]

这种方式的路由会匹配到/users/abc或者/users/123,路径参数用冒号:表示,并且将值abc放在route.params里面

//当前路由/users/abc
import {useRoute} from "vue-router";
const route = useRoute();
console.log(route.params,"route");  // {efg:abc}

注意: 这种路径匹配不上 /users/abc/name,需要把path修改为/users/:efg/name

const routes = [
  // 动态字段以冒号开始
  { path: '/users/:efg/name', component: User },  //这样能匹配上,
]
console.log(route.params,"route");  //{efg:abc}
//或者下面这种写法====================================================================
const routes = [
  // 动态字段以冒号开始
  { path: '/users/:efg/:hhh', component: User },  //这样能匹配上,
]
console.log(route.params,"route");  //{efg:abc,hhh:name}

具体匹配规则

匹配模式 匹配路径 route.params
/users/:username /users/eduardo { username: ‘eduardo’ }
/users/:username/posts/:postId /users/eduardo/posts/123 { username: ‘eduardo’, postId: ‘123’ }
/users-:id(.*) /users-/123 { id: ‘123’ }

需要注意的是,如果路由从/users/abc变到/users/123,因为都是用的同一个component,所以会重复使用,并不会销毁,这就会导致页面不会重新创建,不会触发生命周期钩子,也获取不到最新的route.params,需使用下面的方法来解决

<script setup>
import { watch } from 'vue'
import { useRoute } from 'vue-router'

const route = useRoute()

watch(() => route.params.id, (newId, oldId) => {
  // 对路由变化做出响应...
})
</script>

也可以使用beforeRouteUpdate导航守卫

<script setup>
import { onBeforeRouteUpdate } from 'vue-router'
// ...

onBeforeRouteUpdate(async (to, from) => {
  // 对路由变化做出响应...
  userData.value = await fetchUser(to.params.id)
})
</script>
2.捕获所有路由或 404 Not found 路由

常规参数只匹配url片段之间的字符,用/分隔。如果我们想匹配任意路径,我们可以使用自定义的路径参数正则表达式,在路径参数后面的括号中加入 正则表达式

const routes = [
  // 将匹配所有内容并将其放在 `route.params.pathMatch` 下
  { path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound },
    // 将匹配以 `/user-` 开头的所有内容,并将其放在 `route.params.afterUser` 下
  { path: '/user-:afterUser(.*)', component: UserGeneric },
]

注意: 这种匹配方式的优先级最低,优先级高低为 /test > /test/:id > /test:afterUser(.*)* > /:pathMatch(.*)*,就一句话,越精准的匹配上,优先级越高

二、路由的匹配语法

路由的匹配可以通过正则来更加灵活、精确的匹配

const routes = [
  // /:orderId -> 仅匹配数字
  { path: '/:orderId(\\d+)' },
  // /:productName -> 匹配其他任何内容
  { path: '/:productName' },
]
const routes = [
  // /:chapters ->  匹配 /one, /one/two, /one/two/three, 等
  { path: '/:chapters+' },
  // /:chapters -> 匹配 /, /one, /one/two, /one/two/three, 等
  { path: '/:chapters*' },
]

这个具体就看官网了,难得写了

1.Sensitive 与 strict 路由配置

默认情况下,所有路由是不区分大小写的,并且能匹配带有或不带有尾部斜线的路由。例如,路由/users将匹配 /users/users/、甚至 /Users/。这种行为可以通过strictsensitive选项来修改,它们既可以应用在整个全局路由上,又可以应用于当前路由上:

const router = createRouter({
  history: createWebHistory(),
  routes: [
    // 将匹配 /users/posva 而非:
    // - /users/posva/ 当 strict: true
    // - /Users/posva 当 sensitive: true
    { path: '/users/:id', sensitive: true },
    // 将匹配 /users, /Users, 以及 /users/42 而非 /users/ 或 /users/42/
    { path: '/users/:id?' },
  ],
  strict: true, // 严格匹配,不允许带有尾部斜线
  sensitive: true, //区分大小写
})

三、嵌套路由

在子路由中的path加/和不加有很大的区别,以 /开头的嵌套路径将被视为根路径

  {
    path: "/test",
    name: "test",
    hidden: false,
    component: () => import("@/pages/test/test.vue"),
    children: [
      {
        //path: "center",  //path不加 / ,将匹配到http://localhost:9527/#/test/center
        path: "/center",  //path加 /,将匹配到http://localhost:9527/#/center
        name: "center",
        component: () => import("@/pages/center/center.vue"),
      },
    ],
  },
1.忽略父组件(4.1+版本)
const routes = [
  {
    path: "/test",
    name: "test",
    hidden: false,
    // component: () => import("@/pages/test/test.vue"),
    children: [
      {
        path: "center",
        name: "center",
        component: () => import("@/pages/center/center.vue"),
      },
    ],
  },
]

由于父级没有指定路由组件,*<router-view>将跳过父级并仅使用子路由组件。
在这里插入图片描述

  {
    path: "/test",
    name: "test",
    hidden: false,
    component: () => import("@/pages/test/test.vue"),
    children: [
      {
        path: "center",
        name: "center",
        component: () => import("@/pages/center/center.vue"),
      },
    ],
  },

在这里插入图片描述

四、命名路由

当创建一个路由时,可以选择给路由一个name

const routes = [
  {
    path: '/user/:username',
    name: 'user', 
    component: User
  }
]

这个路由name在整个路由中是唯一的,可以通过router.push({ name: 'user', params: { username: 'erina' } })这种方式来进行路由跳转,将创建一个指向 /user/erina的链接。可以通过route.params来获取参数,使用name有很多优点:

  • 没有硬编码的 URL
  • params的自动编码/解码
  • 防止你在 URL 中出现打字错误
  • 不用关心路由嵌套,就算是100层也能直接使用name来直接定位

命名路由通过params来传递参数,一定要使用动态路由,这样才能获取到传递的参数

### 五、命名视图
有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上用场了。可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果`router-view`没有设置名字,那么默认为 default

```html
<router-view class="view left-sidebar" name="LeftSidebar" />
<router-view class="view main-content" />
<router-view class="view right-sidebar" name="RightSidebar" />
const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    {
      path: '/',
      components: {
        default: Home,
        // LeftSidebar: LeftSidebar 的缩写
        LeftSidebar,
        // 它们与 `<router-view>` 上的 `name` 属性匹配
        RightSidebar,
      },
    },
  ],
})

六、别名

重定向是指当用户访问 /home 时,URL 会被 / 替换,然后匹配成 /。那么什么是别名呢?

/别名为/home,意味着当用户访问/home 时,URL 仍然是 /home,但会被渲染/的内容。

const routes = [
  {
    path: '/users',
    component: UsersLayout,
    children: [
      // 为这 3 个 URL 呈现 UserList
      // - /users
      // - /users/list
      // - /people
      { path: '', component: UserList, alias: ['/people', 'list'] },
    ],
  },
]

如果路由有参数,请确保在任何绝对别名中包含它们:

const routes = [
  {
    path: '/users/:id',
    component: UsersByIdLayout,
    children: [
      // 为这 3 个 URL 呈现 UserDetails
      // - /users/24
      // - /users/24/profile
      // - /24
      { path: 'profile', component: UserDetails, alias: ['/:id', ''] },
    ],
  },
]

注意 如果别名和另一个路由path相同,谁在前面谁显示

const routes = [
 {
        path: "/homePage",
        name: "homePage",
        alias: "/webgpu",  //就算下面有/webgpu的路由,一样会渲染/homePage的内容
        component: () => import("@/pages/home/children/homePage.vue"),
      },
      {
        path: "/webgpu",
        name: "webgpu",
        component: () => import("@/pages/home/children/webgpu.vue"),
      },
]

七、路由组件传参

props设置为true时,route.params将被设置为组件的props

const routes = [
  { path: '/user/:id', component: User, props: true }
]

路由为/user/123456

<!-- User.vue -->
<script setup>
defineProps({
  id: String   //值为123456
})
</script>

<template>
  <div>
    User {{ id }}
  </div>
</template>

props是一个对象时,它将原样设置为组件props。当 props 是静态的时候很有用。

const routes = [
  { path: '/user/:id', component: User, props: {username:"admin"}}
]

可以创建一个返回props的函数。这允许你将参数转换为其他类型,将静态值与基于路由的值相结合等等。

const routes = [
  {
    path: '/search',
    component: SearchUser,
    props: route => ({ abc: "13456" })
  }
]

全局传参

<RouterView v-slot="{ Component }">
  <component
    :is="Component"
    view-prop="value"
   />
</RouterView>

这样每一个路由组件都能获取到view-prop参数

八、路由守卫

1. 全局路由前置守卫
const router = createRouter({ ... })

router.beforeEach((to, from) => {
  //通过一个路由地址重定向到一个不同的地址,如同调用 router.push(),且可以传入诸如 replace: true 或 name: 'home' 之类的选项。它会中断当前的导航,同时用相同的 from 创建一个新导航。
   //  return { name: 'Login' } 
   //返回true或者返回undefine来调用下一个守卫
   //return true
  // 返回 false 以取消导航
  return false
})

如果遇到了意料之外的情况,可能会抛出一个Error。这会取消导航并且调用router.onError()注册过的回调

2. 全局解析守卫

你可以用router.beforeResolve注册一个全局守卫。这和router.beforeEach类似,因为它在每次导航时都会触发,不同的是,解析守卫刚好会在导航被确认之前、所有组件内守卫和异步路由组件被解析之后调用

router.beforeResolve((to,from) =>{
    console.log(to,from,"beforeResolve");
});
3. 在守卫内的全局注入

从 Vue 3.3 开始,你可以在导航守卫内使用inject()方法

// main.ts
const app = createApp(App)
app.provide('global', 'hello injections')

// router.ts or main.ts
router.beforeEach((to, from) => {
  const global = inject('global') // 'hello injections'
  // a pinia store
  const userStore = useAuthStore()
  // ...
})
4. 路由独享的守卫

beforeEnter守卫 只在进入路由时触发,不会在 params、query 或 hash 改变时触发。例如,从 /users/2 进入到 /users/3 或者从 /users/2#info 进入到 /users/2#projects。它们只有在 从一个不同的 路由导航时,才会被触发。

function removeQueryParams(to) {
  if (Object.keys(to.query).length)
    return { path: to.path, query: {}, hash: to.hash }
}

function removeHash(to) {
  if (to.hash) return { path: to.path, query: to.query, hash: '' }
}

const routes = [
  {
    path: '/users/:id',
    component: UserDetails,
    beforeEnter: [removeQueryParams, removeHash],
  },
  {
    path: '/about',
    component: UserDetails,
    beforeEnter: [removeQueryParams],
  },
]

当配合嵌套路由使用时,父路由和子路由都可以使用beforeEnter。如果放在父级路由上,路由在具有相同父级的子路由之间移动时,它不会被触发。例如:

const routes = [
  {
    path: '/user',
    beforeEnter() {
      // ...
    },
    children: [
      { path: 'list', component: UserList },
      { path: 'details', component: UserDetails },
    ],
  },
]

示例中的 beforeEnter 在 /user/list /user/details 之间移动时不会被调用,因为它们共享相同的父级路由。如果我们直接将 beforeEnter 守卫放在details路由上,那么在这两个路由之间移动时就会被调用。

5. 完整的导航解析流程
  • 导航被触发。
  • 在失活的组件里调用 beforeRouteLeave 守卫。
  • 调用全局的 beforeEach 守卫。
  • 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
  • 在路由配置里调用 beforeEnter。
  • 解析异步路由组件。
  • 在被激活的组件里调用 beforeRouteEnter。
  • 调用全局的 beforeResolve 守卫(2.5+)。
  • 导航被确认。
  • 调用全局的 afterEach 钩子。
  • 触发 DOM 更新。
  • 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

九、RouterView 插槽

RotuerView 组件暴露了一个插槽,可以用来渲染路由组件:

<router-view v-slot="{ Component }">
  <keep-alive>
    <component :is="Component" />
  </keep-alive>
</router-view>

十、滚动行为

使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 vue-router 能做到,而且更好,它让你可以自定义路由切换时页面如何滚动。
注意: 这个功能只在支持 history.pushState 的浏览器中可用。

const router = createRouter({
  history: createWebHashHistory(),
  routes: [...],
  scrollBehavior (to, from, savedPosition) {
  	return {
  		top:10,
  		behavior: 'smooth',  //让滚动更平滑,不是直接跳到这个位置,而是慢慢滚动到这个位置
  	}
    // return 期望滚动到哪个的位置
  }
})

也可以通过 el 传递一个 CSS 选择器或一个 DOM 元素。在这种情况下,top 和 left 将被视为该元素的相对偏移量

const router = createRouter({
  scrollBehavior(to, from, savedPosition) {
    // 始终在元素 #main 上方滚动 10px
    return {
      // 也可以这么写
      // el: document.getElementById('main'),
      el: '#main',
      // 在元素上 10 像素
      top: 10,
    }
  },
})

十一、导航故障

当使用router-link组件时,Vue Router会自动调用router.push来触发一次导航。虽然大多数链接的预期行为是将用户导航到一个新页面,但也有少数情况下用户将留在同一页面上:

  • 用户已经位于他们正在尝试导航到的页面
  • 一个导航守卫通过调用return false中断了这次导航
  • 当前的导航守卫还没有完成时,一个新的导航守卫会出现了
  • 一个导航守卫通过返回一个新的位置,重定向到其他地方 (例如,return ‘/login’)
  • 一个导航守卫抛出了一个Error

注意:导航是异步的
如果导航被阻止,导致用户停留在同一个页面上,由router.push返回的Promise的解析值将是Navigation Failure。否则,它将是一个falsy值(通常是undefined)。这样我们就可以区分我们导航是否离开了当前位置:

const navigationResult = await router.push('/my-profile')

if (navigationResult) {
  // 导航被阻止
} else {
  // 导航成功 (包括重新导航的情况)
  this.isMenuOpen = false
}

Navigation Failure 是带有一些额外属性的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有三种值

  • aborted:在导航守卫中返回false中断了本次导航。
  • cancelled: 在当前导航完成之前又有了一个新的导航。比如,在等待导航守卫的过程中又调用了router.push
  • duplicated:导航被阻止,因为我们已经在目标位置了。

十二、动态路由

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