1. 安装
初始化Vue3项目(推荐使用vite):
// vue
npm init vue@latest
// vite
npm init vite@latest
安装vue-router:
npm i vue-router -S
2. 引入路由
使用(npm init vue@latest)初始化项目时,选择router后,会自动安装并使用 vue-router。使用(npm init vite@latest)时,需要手动引入路由:
- 在src文件夹下创建router文件,并定义index.ts:
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
redirect: '/login'
},
{
path: '/login',
name: 'login',
// 动态引入,可以实现组件懒加载以及代码分包
component: () => import('../views/HomeView.vue')
},
{
path: '/register',
name: 'register',
component: () => import('../views/AboutView.vue')
}
]
})
export default router
- main.ts 中引入路由
import router from './router'
app.use(router)
- app.vue 中使用路由:
// 路由连接
<RouterLink to="/">login</RouterLink>
<RouterLink to="/register">register</RouterLink>
// 路由容器
<RouterView></RouterView>
3. vue-router4 与 vue-router3 的区别
- 引入router的方式:
// vue-router3中:
import Router from 'vue-router'
// vue-router4 中, 引入创建 router 的 hooks:
import { createRouter } from 'vue-router'
const router = createRouter(options)
- 路由模式
vue-router3中模式选项为 mode,而 vue-router4中模式选项对应的是 history。
router3 中三种模式与router4中三种模式对应关系:
哈希模式(url带#号):
router3 hash ===> router4 createWebHashHistory ;
history模式:
router3 history ===> router4 createWebHistory ;
服务端渲染 SSR:
router3 abstact ===> router4 createMemoryHistory
hash 模式是基于 window.location.hash 实现的,这种模式会在url中生成一个#号,#后面包括#就是hash,我们可以通过添加事件 hashchange 来监听url的变化。
history 模式是基于 H5 中的 history API 来实现的。我们可以通过 popstate 事件来监听url的变化,跳转到新页面时可以使用 history.pushState({}, title, ‘/login’),但是这个方法只会改变url的路径,并不会被 popstate 监听到,所以我们调用这个方法后,需要手动刷新。
4. 编程式导航
使用 router-link 跳转时,to 属性如果直接写地址的话,容易写错,我们可以使用name 属性:
<RouterLink :to="{name: 'login'}">login</RouterLink>
使用 replace 不产生历史记录:
<RouterLink replace :to="{name: 'login'}">login</RouterLink>
如果不想使用 router-link 路由链接进行跳转时,我们可以使用编程式导航:
import { useRouter } from 'vue-router';
const router = useRouter()
const toPage = (path: string) => {
// 字符串 方式
router.push(path)
// 对象 方式
/* router.push({
path
}) */
// 命名式 path 对应的式路由的 name 值
/* router.push({
name: path
}) */
// replace 不产生历史记录
// router.replace(path)
}
// 向前 2 级
const next = () => {
router.go(2)
}
// 后退 1 级
const back = () => {
router.back()
}
// 向前 1 级
const forward = () => {
router.forward()
}
5. 路由传参
vue-router 路由传参有两种方式: query 传参 和 params 传参
query 传参(参数会显示在url上):
// 传参
import { useRouter } from 'vue-router';
router.push({
path: '/register', // 可以用 path ,也可以用 name
// name: 'register',
query: item
})
// 接收
import { useRoute } from 'vue-router';
const route = useRoute()
const query = route.query
params 传参(参数不会显示在url上,但刷新页面参数会丢失):
Vue Router的2022-8-22 这次更新后,params 无法向之前 那样进行获取
之前的写法:
router.push({
name: 'register', // 这里必须要用 name
params: item
})
// 接收
const route = useRoute()
const params = route.params
现在可以使用这几种方式:
- 将 params 存储在 pinia 或 vuex 仓储中
- 使用动态路由匹配
// 路由:
{
path:'/register/:id/:name/:price',
name: 'register',
component: () => import('../views/AboutView.vue')
}
// 跳转:
router.push({
name: 'register',
params: item // 这里的参数要与路由中定义的参数一一对应
})
- 使用 History API
// 跳转:
router.push({
name: 'register',
state: item
})
// 接收:
const historyParams = history.state
6. 嵌套路由和命名视图
如果想做一个菜单栏,点击不同菜单,显示不同的内容,我们可以使用嵌套路由,将菜单作为父路由,内容区作为子路由。如果路由对应多个组件,可以使用命名视图,通过多个不同 name 属性的 routerview,来渲染对应的组件。
修改路由,index.ts:
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/user',
name: 'user',
component: () => import('../views/RootView.vue'),
// 使用 children 属性,为父路由添加子路由
children: [
{
path: 'user1',
name: 'user1',
// 注意这里是 components,如果不是对象,写 component
components: {
// default 对应下方视图 <RouterView></RouterView>
default: () => import('../components/A.vue')
}
},
{
path: 'user2',
name: 'user2',
// 多个组件要写 components
components: {
// viewB 对应 <RouterView name="viewB"></RouterView>
viewB: () => import('../components/B.vue'),
// viewC 对应 <RouterView name="viewC"></RouterView>
viewC: () => import('../components/C.vue')
}
},
]
}
]
})
父组件,RootView.vue:
<div>
<h1>这是ROOT</h1>
<RouterLink to="/user/user1">user1</RouterLink>
<RouterLink to="/user/user2">user2</RouterLink>
<RouterView></RouterView>
// name 属性 指定渲染哪一个组件
<RouterView name="viewB"></RouterView>
<RouterView name="viewC"></RouterView>
</div>
7. 路由重定向 redirect 与 路由别名 alias
如果两个路由访问的页面是相同的,我们可以使用路由重定向:
例如 根路由 ‘/’ 自动跳转到登录页 ‘/login’ :
{
path: '/',
// 字符串形式
redirect: '/login'
},
redirect 属性有三种写法,上述是 字符串形式。还有对象和回调函数形式。
对象形式:
{
path: '/',
// 字符串形式
redirect: {
name: 'login'
}
},
回调函数形式:
{
path: '/',
// 字符串形式
redirect: to => {
console.log('to', to);
return {
name: 'login',
query: {
id: 1
}
}
}
},
回调函数有个参数,这个参数包含路由相关的一些信息,比如 path、query、params、hash 等。
通过 alias 属性,我们可以给路由起一个别名,通过访问别名也可以访问相同页面:
{
path: '/',
alias: ['/main', '/main2'],
component: () => import('../views/HomeView.vue')
},
别名可以起一个,也可以起多个,一个的话 alias 类型为字符串,多个就用 数组。
8. 导航守卫
全局前置守卫
如果希望用户未登录的时候,访问其他页面时,自动跳转到登录页,还有开启进度条,我们可以给路由添加一个前置守卫:
const whiteList = ['/', '/login'] // 白名单 免登录
router.beforeEach((to, from, next) => {
// 开启进度条
NProgress.start()
console.log(to.path);
if(whiteList.includes(to.path) || localStorage.getItem('token')){
next()
} else {
next('/')
}
})
其中 to 表示 去哪里,from 表示从 哪里来,next 表示下一次去哪里,其中 next() 必须被调用。
全局后置守卫
一般在全局后置守卫中,我们可以做一些操作,比如关闭进度条:
// 路由后置守卫
router.afterEach((to, from) => {
setTimeout(()=>{
console.log(to, from);
// 关闭进度条
NProgress.done()
}, 1000)
})
后置守卫只有两个参数,其中 to 和 from 与前置守卫相同。
9. 路由元信息
路由元信息就是路由规则 route 中的 meta 对象,我们可以在 meta 中添加一些信息,比如标题 title,动画效果 transition,操作权限 等等,等到我们跳转到这个页面时,获取到路由的元信息,来对页面进行一些修改。下面举个例子:
为路由添加 title 和 transition,router/index.ts:
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router'
import NProgress from 'nprogress'
import '@/assets/nprogress.less'
NProgress.configure({ showSpinner: true })
// 定义 元信息 的类型
declare module 'vue-router' {
interface RouteMeta{
title: string
transition: string
}
}
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/login',
name: 'login',
component: () => import('../views/HomeView.vue'),
meta: {
title: '登录',
transition: 'animate__fadeInLeft'
}
},
{
path: '/register',
name: 'register',
component: () => import('../views/AboutView.vue'),
meta: {
title: '注册',
transition: 'animate__fadeInDown'
}
}
]
})
// 路由前置守卫
const whiteList = ['/', '/login', '/register'] // 白名单
router.beforeEach((to, from, next) => {
// 开启进度条
NProgress.start()
if(whiteList.includes(to.path) || localStorage.getItem('token')){
// 获取 元信息中的 title ,修改页面标题
document.title = to.meta.title
next()
} else {
document.title = from.meta.title
next('/')
}
})
// 路由后置守卫
router.afterEach((to, from) => {
setTimeout(()=>{
// 关闭进度条
NProgress.done()
}, 1000)
})
export default router
app.vue:
<template>
<div>
<h1>demo</h1>
<!-- 路由导航 -->
<RouterLink replace to="/">login</RouterLink>
<RouterLink replace to="/register">register</RouterLink>
<RouterView #default="{route, Component}">
<!-- router-view 默认有一个slot,传递两个参数 route 和 Component -->
<transition :enter-active-class="`animate__animated ${route.meta.transition}`">
<!-- 注意添加动画的组件只能有一个根元素 -->
<component :is="Component"></component>
</transition>
</RouterView>
</div>
</template>
<script setup lang='ts'>
import 'animate.css'
</script>
上面的栗子中,我们通过获取路由中的元信息,修改页面标题,并为路由组件添加不同的动画效果。
10. 滚动行为
平时我们会发现这种情况,读一篇文章,读到一半后切换到另一个页面,然后再切换回来时,页面会自动滚动到之前看到的位置。这种情况我们可以使用路由的滚动行为 scrollBehavior 来实现:
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
// 滚动行为
scrollBehavior: (to, from, savePosition) => {
console.log(savePosition); // {top: 0, left: 0}
if(savePosition){
return savePosition
} else {
return {top: 0}
}
},
routes: [
{
path: '/login',
name: 'login',
component: () => import('../views/HomeView.vue')
},
{
path: '/register',
name: 'register',
component: () => import('../views/AboutView.vue')
}
]
})
scrollBehavior 是一个函数,这个函数有三个参数: to, from , savePosition。其中 to,from 和路由守卫中的相同,savePosition 是一个对象,记录了路由组件滚动的位置,{top: 0, left: 0},top 表示距离页面顶部的位置,left 表示距离页面左侧的位置。如果页面没有滚动区域,则 savePosition 的值为 null。这个函数需要返回一个对象,router会根据这个对象,滚动到相应的位置。
scrollBehavior 支持异步:
scrollBehavior: (to, from, savePosition) => {
// 异步
return new Promise((r) => {
setTimeout(()=>{
r({
top: 100
})
}, 2000)
})
}
11. 动态路由
一般情况下,我们会根据后端返回的菜单列表,动态的渲染路由,实现原理就是使用 router.addRoute() 和 router.removeRoute() ,向路由表中添加或删除路由匹配规则,实现不同的用户看到的菜单是不同的。