一、动态路由匹配
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/
。这种行为可以通过strict
和sensitive
选项来修改,它们既可以应用在整个全局路由上,又可以应用于当前路由上:
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/centerpath: "/center", //path加 /,将匹配到http://localhost:9527/#/centername: "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', ''] },],},
]
注意 别名的优先级高于匹配到的路径
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 storeconst 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 上方滚动 10pxreturn {// 也可以这么写// 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', component: AdminSettings })
动态添加路由有两种方式,上面的第一种是直接添加在最顶级路由,第二种方式传入一个路由的name
,可以把第二个参数的路由放在它的children
属性中,用于添加嵌套路由,相当于
router.addRoute({name: 'admin',path: '/admin',component: Admin,children: [{ path: 'settings', component: AdminSettings }],
})