目录
安装
- 现有项目通过npm 安转(vue3需要安装4.X)npm install vue-router@4
npm install vue-router@4
- 创建Vue3工程时可以直接选择添加router
基本使用
创建路由器实例
src/router/index.ts 文件中开始创建路由器实例
import { createMemoryHistory, createRouter } from 'vue-router'
import HomeView from './HomeView.vue'
import AboutView from './AboutView.vue'
const routes = [
{ path: '/', component: HomeView },
{ path: '/about', component: AboutView },
]
const router = createRouter({
history: createMemoryHistory(),
routes,
})
注册路由器插件
main.ts中开始注册路由器插件
路由器插件主要作用
- 全局注册 RouterView和RouterLink 组件
- 启用useRouter() 和useRoute()组合式函数
- 触发路由器解析初始路由
createApp(App)
.use(router)
.mount('#app')
等价于
const app = createApp(App)
app.use(router)
app.mount('#app')
动态路由匹配
路径参数 用冒号
:表示。当一个路由被匹配时,它的 params 的值将在每个组件中以route.params的形式暴露出来
import User from './User.vue'
// 这些都会传递给 `createRouter`
const routes = [
// 动态字段以冒号开始
{ path: '/users/:id', component: User },
]
同一个路由可以设置多个路径参数,它会映射到route.params上的对应字段
| 匹配模式 | 匹配路径 | route.params |
|---|---|---|
| /users/:username | /users/eduardo | { username: 'eduardo' } |
| /users/:username/posts/:postId | /users/eduardo/posts/123 | { username: 'eduardo', postId: '123' } |
可重复参数
需要匹配具有多个部分的路由,如
/first/second/third,你应该用*(0 个或多个)和+(1 个或多个)将参数标记为可重复:
const routes = [
// /:chapters -> 匹配 /one, /one/two, /one/two/three, 等
{ path: '/:chapters+' },
// /:chapters -> 匹配 /, /one, /one/two, /one/two/three, 等
{ path: '/:chapters*' },
]
这将为你提供一个参数数组,而不是一个字符串,并且在使用命名路由时也需要你传递一个数组
// 给定 { path: '/:chapters*', name: 'chapters' },
router.resolve({ name: 'chapters', params: { chapters: [] } }).href
// 产生 /
router.resolve({ name: 'chapters', params: { chapters: ['a', 'b'] } }).href
// 产生 /a/b
// 给定 { path: '/:chapters+', name: 'chapters' },
router.resolve({ name: 'chapters', params: { chapters: [] } }).href
// 抛出错误,因为 `chapters` 为空
可选参数
可以通过使用
?修饰符(0 个或 1 个)将一个参数标记为可选:
const routes = [
// 匹配 /users 和 /users/posva
{ path: '/users/:userId?' },
// 匹配 /users 和 /users/42
{ path: '/users/:userId(\d+)?' },
]
嵌套路由
一个被渲染的组件也可以包含自己嵌套的
<router-view>,要将组件渲染到这个嵌套的router-view中,我们需要在路由中配置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,
},
],
},
]
命名路由
相当于给路由取一个别名
const routes = [
{
path: '/user/:username',
name: 'profile',
component: User
}
]
使用
name而不是path来传递to属性给<router-link>
<router-link :to="{ name: 'profile', params: { username: 'erina' } }">
User profile
</router-link>
注意: 所有路由的命名必须是唯一的,如果有相同的命名,路由器只会保留最后一条
编程式导航
路由组件的两个重要的属性:$route和$router变成了两个hooks
import {useRoute,useRouter} from 'vue-router'
const route = useRoute()
const router = useRouter()
console.log(route.query)
console.log(route.parmas)
console.log(router.push)
console.log(router.replace)
导航到不同的 URL,可以使用
router.push方法。这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,会回到之前的 URL。当你点击
<router-link>时,内部会调用这个方法,所以点击<router-link :to="...">相当于调用router.push(...):
| 声明式 | 编程式 |
|---|---|
<router-link :to="..."> | router.push(...) |
// 字符串路径
router.push('/users/eduardo')
// 带有路径的对象
router.push({ path: '/users/eduardo' })
// 命名的路由,并加上参数,让路由建立 url
router.push({ name: 'user', params: { username: 'eduardo' } })
// 带查询参数,结果是 /register?plan=private
router.push({ path: '/register', query: { plan: 'private' } })
// 带 hash,结果是 /about#team
router.push({ path: '/about', hash: '#team' })
注意1:传递
params参数时,若使用to的对象写法,必须使用name配置项,不能用path。使用path时要手写完整的带有参数的path注意2:传递
params参数时,需要提前在规则中占位。
路由组件传参(props)
三种传参方式
{
name:'xiang',
path:'detail/:id/:title/:content',
component:Detail,
// props的对象写法,作用:把对象中的每一组key-value作为props传给Detail组件
// props:{a:1,b:2,c:3},
// props的布尔值写法,作用:把收到了每一组params参数,作为props传给Detail组件
// props:true
// props的函数写法,作用:把返回的对象中每一组key-value作为props传给Detail组件
props(route){
return route.query
}
}
重定向
将特定的路径,重新定向到已有路由。
{
path:'/',
redirect:'/about'
}
路由元信息
meta属性可以用来实现谁可以访问路由,以及一切任意信息附加到路由上,并且它可以在路由地址和导航守卫上都被访问到
const routes = [
{
path: '/posts',
component: PostsLayout,
children: [
{
path: 'new',
component: PostsNew,
// 只有经过身份验证的用户才能创建帖子
meta: { requiresAuth: true },
},
{
path: ':id',
component: PostsDetail
// 任何人都可以阅读文章
meta: { requiresAuth: false },
}
]
}
]
可以通过route.meta去访问meta字段
router.beforeEach((to, from) => {
// 而不是去检查每条路由记录
// to.matched.some(record => record.meta.requiresAuth)
if (to.meta.requiresAuth && !auth.isLoggedIn()) {
// 此路由需要授权,请检查是否已登录
// 如果没有,则重定向到登录页面
return {
path: '/login',
// 保存我们所在的位置,以便以后再来
query: { redirect: to.fullPath },
}
}
})
导航守卫
全局前置守卫
当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于等待中。
接受两个参数--to,from
to: 即将要进入的目标from: 当前导航正要离开的路由
const router = createRouter({ ... })
router.beforeEach((to, from) => {
// ...
// 返回 false 以取消导航
return false
})
可以返回的值
false
取消当前的导航。如果浏览器的 URL 改变了(可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到
from路由对应的地址。
一个路由地址
通过一个路由地址重定向到一个不同的地址,如同调用
router.push(),且可以传入诸如replace: true或name: 'home'之类的选项。它会中断当前的导航,同时用相同的from创建一个新导航。
next
之前的router版本中可以使用,但是新版本已经删除,不过仍然支持,不过使用的时候,确保
next在任何给定的导航守卫中都被严格调用一次。
// GOOD
router.beforeEach((to, from, next) => {
if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
else next()
})
全局解析守卫
router.beforeResolve注册一个全局守卫。它在每次导航时都会触发,不同的是,解析守卫刚好会在导航被确认之前、所有组件内守卫和异步路由组件被解析之后调用。
router.beforeResolve(async to => {
if (to.meta.requiresCamera) {
try {
await askForCameraPermission()
} catch (error) {
if (error instanceof NotAllowedError) {
// ... 处理错误,然后取消导航
return false
} else {
// 意料之外的错误,取消导航并把错误传给全局处理器
throw error
}
}
}
})
全局后置钩子
不会接受next函数,也不会改变导航,更多的是 分析、更改页面标题、声明页面等辅助功能
router.afterEach((to, from) => {
sendToAnalytics(to.fullPath)
})
组件内的守卫
可以为路由组件添加以下配置:
beforeRouteEnterbeforeRouteUpdatebeforeRouteLeave
<script>
export default {
beforeRouteEnter(to, from) {
// 在渲染该组件的对应路由被验证前调用
// 不能获取组件实例 `this` !
// 因为当守卫执行时,组件实例还没被创建!
},
beforeRouteUpdate(to, from) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
// 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
},
beforeRouteLeave(to, from) {
// 在导航离开渲染该组件的对应路由时调用
// 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
},
}
</script>
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用
beforeRouteLeave守卫。 - 调用全局的
beforeEach守卫。 - 在重用的组件里调用
beforeRouteUpdate守卫(2.2+)。 - 在路由配置里调用
beforeEnter。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter。 - 调用全局的
beforeResolve守卫(2.5+)。 - 导航被确认。
- 调用全局的
afterEach钩子。 - 触发 DOM 更新。
- 调用
beforeRouteEnter守卫中传给next的回调函数,创建好的组件实例会作为回调函数的参数传入。
路由模式
Hash模式
以#分割实际的URL,#以后的部分不会被当做实际的URL,所以不需要在服务器端进行特殊处理,但是它影响SEO优化
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
history: createWebHashHistory(),
routes: [
//...
],
})
History模式(HTML5模式)
History模式会使URL看起来比较符合正常的地址路径,但是对于SPA应用来说,需要在服务器端进行配置
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
//...
],
})
Memory模式
不会与 URL 交互也不会自动触发初始导航。这使得它非常适合 Node 环境和 SSR。它是用
createMemoryHistory()创建的,并且需要你在调用app.use(router)之后手动 push 到初始导航。它不会有历史记录,所以也就无法前进或者后退
import { createRouter, createMemoryHistory } from 'vue-router'
const router = createRouter({
history: createMemoryHistory(),
routes: [
//...
],
})
面试重点
导航守卫中next()的三种用法?
next() // 正常放行
next(false) // 中断当前导航
next('/login') // 重定向到新路径
Vue3中路由API的变化?
- 使用
createRouter替代new VueRouter history配置项独立(createWebHistory,createWebHashHistory)$route和$router变成了两个hooks(useRouter和useRoute)
Vue Router两种模式的区别?
- hash模式:使用URL hash值(#后的部分),兼容性好,无需服务器配置
- history模式:依赖HTML5 History API,需要服务器配置支持
- memory模式:适用于非浏览器环境(如Electron)
如何实现路由权限控制?
// 方案一:全局守卫 + 路由元信息
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !store.state.auth) {
next('/login')
} else {
next()
}
})
// 方案二:动态路由 + 用户权限验证
function setupRoutes(userRole) {
const routes = filterRoutesByRole(userRole)
routes.forEach(route => router.addRoute(route))
}
性能优化方案?
路由懒加载
Vue Router 支持动态导入,这意味着你可以用动态导入代替静态导入,
// 将
// import UserDetails from './views/UserDetails.vue'
// 替换成
const UserDetails = () => import('./views/UserDetails.vue')
const router = createRouter({
// ...
routes: [
{ path: '/users/:id', component: UserDetails }
// 或在路由定义里直接使用它
{ path: '/users/:id', component: () => import('./views/UserDetails.vue') },
],
})
把组件按组分块
webpack实现方案
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
export default defineConfig({
build: {
rollupOptions: {
// https://rollupjs.org/guide/en/#outputmanualchunks
output: {
manualChunks: {
'group-user': [
'./src/UserDetails',
'./src/UserDashboard',
'./src/UserProfileEdit',
],
},
},
},
},
})
预加载关键路由
// 在用户登录后预加载管理界面
router.beforeEach((to, from, next) => {
if (to.name === 'Dashboard') {
import('./views/AdminPanel.vue')
}
next()
})
Keep-Alive缓存
<router-view v-slot="{ Component }">
<keep-alive :include="['Home', 'User']">
<component :is="Component" />
</keep-alive>
</router-view>