路由简介
- 后端路由:根据不同的用户 URL 请求,服务器返回不同的内容
本质是:URL 请求与服务器资源之间的对应关系 - 前端路由:根据不同的用户事件,显示不同的页面内容
本质是:用户事件与事件处理函数之间的对应关系
- SPA,单页面应用程序:不会打开新页面,支持前后回退
Vue Router
- Vue Router 是 Vue.js 的路由管理器,可以简便地构建单页面应用
- 未使用的路由组件会被自动销毁,使用时再重新创建
基本使用
-
引入 Vue Router:
npm install vue-router@4
-
创建 router 文件:创建 router 文件夹,在该文件夹下创建 router 文件
router
---- index.js // 主 router 文件
index.js:主路由文件。在路由文件中,设置路由规则,并创建 VueRouter 实例
懒加载:component: () => import("vue 组件路径"),
一般除了初始页面,其他页面都使用懒加载
import Vue from 'vue'
import VueRouter from 'vue-router'
// 引入组件 Home
import Home from '../views/Home.vue'
// 通过插件启动,该指令需要在 VueRouter 创建前调用
Vue.use(VueRouter)
// 设置路由规则
const routes = [{
path: '/', // 设置路由路径
name: 'Home', // 设置组件名称
component: Home // 使用组件 Home
},
{
path: '/about',
name: 'About',
// 懒加载
component: () => import('../views/About.vue')
}
]
// 创建 VueRouter 实例
const router = new VueRouter({
routes
})
export default router
- 在入口文件 main.js 中导入主 router 文件
import Vue from 'vue'
import App from './App.vue'
import router from './router' // 引入 router 文件
import store from './store'
Vue.config.productionTip = false
new Vue({
router, // 将 router 文件导入到 Vue 中
store,
render: h => h(App)
}).$mount('#app')
- 定义路由组件:
页面存储在 views 文件夹下:
views
---- About.vue
---- Home.vue
组件存储在 components 文件夹下:
components
---- HelloWorld.vue
- 在主 Vue Component (App) 中添加路由
添加路由链接:<router-link to="/路由路径"> 内容 </router-link>
添加路由占位符:<router-view> </router-view>
||<router-view />
<template>
<div id="app">
<div id="nav">
<!-- 添加路由链接,并设置 to 属性 -->
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<!-- 添加路由占位符 -->
<router-view />
</div>
</template>
<script>
export default {
name: "App",
};
</script>
路由嵌套
- 设置路由规则:
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [{
path: '/home',
name: 'Home',
component: Home,
children: [{ // 设置 children 属性添加子路由,属性值为数组
path: 'son', // 子路由的 path 属性值不需要 / 前缀
name: 'Son',
component: () => import('../views/Son.vue')
}]
}]
const router = new VueRouter({
routes
})
export default router
- 配置组件:
设置路由路径时:
① 直接在 router-link 标签中设置 to 属性,属性值就是路由路径
② 绑定 to 属性,以对象形式,通过组件的 name 属性值,设置路由路径
③ 绑定 to 属性,以对象形式,通过组件的 path 属性值,设置路由路径
<template>
<div id="app">
<div id="nav">
<!-- 直接写路由路径 -->
<router-link to="/home">Home</router-link>
<!-- 绑定 to 属性,以对象的形式,通过组件的 name 属性,设置路由路径 -->
<!-- <router-link :to="{ name: 'Home' }">Home</router-link> -->
<!-- 绑定 to 属性,以对象的形式,通过组件的 path 属性,设置路由路径 -->
<!-- <router-link :to="{ path: '/home' }">Home</router-link> -->
</div>
<router-view />
</div>
</template>
<script>
export default {
name: "App",
};
</script>
<template>
<div class="home">
<h1>This is an Home page</h1>
<hr />
<router-link to="/home/son">Son</router-link>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "Home",
};
</script>
<template>
<div class="son">
<h2>Son.vue</h2>
</div>
</template>
<script>
export default {
name: "Son",
};
</script>
query
query 用于:父路由给子路由传递数据
① 绑定 to 属性,以对象的形式,设置路由路径 并传递父路由中的数据
② 绑定 to 属性,直接写路由路径 并传递父路由中的数据
<template>
<div class="home">
<h1>This is an Home page</h1>
<ul>
<!-- 遍历所有的数据 -->
<li v-for="item in arr" :key="item.id">
<!-- 绑定 to 属性,以对象的形式,设置路由路径并传递父组件中的数据 -->
<router-link
:to="{
// 通过组件的 name 属性,设置路由路径
name: 'Son',
// 通过组件的 path 属性,设置路由路径
// path: '/home/son',
// 通过 query 属性,传递父组件中的数据
query: {
way: '对象',
id: item.id,
name: item.hero,
},
}"
>{{ item.hero }}
</router-link>
|
<!-- 绑定 to 属性,以字符串模版的形式,设置路由路径并传递父组件中的数据 -->
<router-link
:to="`/home/son?way=字符串模版&id=${item.id}&name=${item.hero}`"
>{{ item.hero }}
</router-link>
</li>
</ul>
<hr />
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "Home",
data() {
return {
arr: [
{
id: "001",
hero: "卡莎",
},
{
id: "002",
hero: "火男",
},
{
id: "003",
hero: "卡萨",
},
],
};
},
};
</script>
- 在子路由中获取父路由中的数据:
&route
:点击的路由组件的信息对象
&router
:VueRouter 的实例化对象 - 通过当前的路由对象 &route 取值:
&route.query.属性值
<template>
<div class="son">
<!-- 在子组件中,通过 “ &route.query.属性值 ” 获取父组件中的数据 -->
<p>way:{{ $route.query.way }}</p>
<p>id:{{ $route.query.id }}</p>
<p>name:{{ $route.query.name }}</p>
</div>
</template>
<script>
export default {
name: "Son",
};
</script>
params
除了通过 query 传参,我们还可以通过 params 传参
此时,router 文件该如下设置:接收参数的路由的 path 属性值应改成如下格式
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [{
path: '/home',
name: 'Home',
component: Home,
children: [{
path: 'son/:way/:id/:name', // 动态路由
name: 'Son',
component: () => import('../views/Son.vue')
}]
}]
const router = new VueRouter({
routes
})
export default router
- 父路由通过对象 / 字符串模版的形式传参,相比较 query,需要修改一下格式
- 通过 params 接收数据时,不可以使用 path 属性设置路由,需要使用 name 属性设置路由
<template>
<div class="home">
<h1>This is an Home page</h1>
<ul>
<!-- 遍历所有的数据 -->
<li v-for="item in arr" :key="item.id">
<!-- 绑定 to 属性,以对象的形式,设置路由路径并传递父组件中的数据 -->
<router-link
:to="{
// 通过组件的 name 属性,设置路由路径
name: 'Son',
// 通过 params 属性,传递父组件中的数据
params: {
way: '对象',
id: item.id,
name: item.hero,
},
}"
>{{ item.hero }}
</router-link>
|
<!-- 绑定 to 属性,以字符串模版的形式,设置路由路径并传递父组件中的数据 -->
<router-link
:to="`/home/son/字符串模版/${item.id}/${item.hero}`"
>{{ item.hero }}
</router-link>
</li>
</ul>
<hr />
<router-view></router-view>
</div>
</template>
子路由通过 &route.params.属性值
获取参数:
<template>
<div class="son">
<!-- 在子组件中,通过 “ &route.params.属性值 ” 获取父组件中的数据 -->
<p>way:{{ $route.params.way }}</p>
<p>id:{{ $route.params.id }}</p>
<p>name:{{ $route.params.name }}</p>
</div>
</template>
<script>
export default {
name: "Son",
};
</script>
props
在 router 文件中设置 props 属性,可较方便地在路由中接收参数。简化参数的传递
const routes = [{
path: '/home',
name: 'Home',
component: Home,
children: [{
path: 'son/:way/:id/:name', // 动态路由
name: 'Son',
component: () => import('../views/Son.vue'),
// 方法1:props 对象,传递属性及其属性值
props: {
msg: "props中的数据"
}
}]
}]
<template>
<div class="son">
<!-- 在子组件中,通过 “ &route.params.属性值 ” 获取父组件中的数据 -->
<p>way:{{ $route.params.way }}</p>
<p>id:{{ $route.params.id }}</p>
<p>name:{{ $route.params.name }}</p>
<!-- 在 template 标签中直接使用 -->
<p>{{ msg }}</p>
</div>
</template>
<script>
export default {
name: "Son",
props: ["msg"], // 在路由中设置 props 属性接收数据
};
</script>
// 方法2:布尔值,表示父路由的所有数据都通过 props 传递
props: true
<template>
<div class="son">
<!-- 在 template 标签中直接使用 -->
<p>way:{{ way }}</p>
<p>id:{{ id }}</p>
<p>name:{{ name }}</p>
</div>
</template>
<script>
export default {
name: "Son",
props: ["way", "id", "name"], // 在路由中设置 props 属性接收数据
};
</script>
// 方法3:方法,接收 1 个参数,为 $route 对象
props(route) {
return {
id: route.params.id,
name: route.params.name,
way: route.params.way,
}
}
需要注意的是,方式 1 2 都只可以传递 params 参数;方式 3 还可以传递 query 参数
const routes = [{
path: "/",
name: "Hero",
component: Hero,
children: [{
path: "myself",
name: "MyHero",
component: MyHero,
children: [{
// query 使用静态路由地址
path: "show",
name: "SomeOne",
component: SomeOne,
// 接收 $route 对象作为参数
props(route) {
return {
// 这里通过 query 获取数据
id: route.query.id,
name: route.query.name,
way: route.query.way,
}
}
}]
}, {
path: "his",
name: "HisHero",
component: HisHero,
}]
}, {
path: "/about",
name: "About",
component: () =>
import("../views/About.vue"),
}, ];
<template>
<div class="home">
<h1>This is an Home page</h1>
<ul>
<!-- 遍历所有的数据 -->
<li v-for="item in arr" :key="item.id">
<!-- 绑定 to 属性,以对象的形式,设置路由路径并传递父组件中的数据 -->
<router-link
:to="{
// 通过组件的 name 属性,设置路由路径
name: 'Son',
// 通过 query 属性,传递父组件中的数据
query: {
way: '对象',
id: item.id,
name: item.hero,
},
}"
>{{ item.hero }}
</router-link>
|
<!-- 绑定 to 属性,以字符串模版的形式,设置路由路径并传递父组件中的数据 -->
<router-link
:to="`/home/son?way=字符串模版&id=${item.id}&name=${item.hero}`"
>{{ item.hero }}
</router-link>
</li>
</ul>
<hr />
<router-view></router-view>
</div>
</template>
路由重定向
rediract:"指定路由地址"
,原本链接到 a 地址 → 链接到 b 地址
let router = new VueRouter({
routes: [{
path: "/", // 原路由地址
redirect: "/user" // 重定向的路由地址
}, {
path: '/user',
component: User
}, {
path: '/register',
component: register
}]
});
组件缓存
- 组件不用时,默认会被销毁
- 此时我们可以使用
keep-alive
标签包裹路由出口router-view
此时,在该路由出口显示的所有路由都不会被销毁
<keep-alive><router-view /></keep-alive>
- 我们可以设置
include="组件名"
|
此时只有指定组件会被缓存
<keep-alive include="Home"><router-view /></keep-alive>
- 当需要缓存多个组件时,我们可以绑定 include 属性,并传入一个数组参数
<keep-alive :include="['Home', 'About']"><router-view /></keep-alive>
- 我们也可以设置 exclude 属性,表示除了指定组件,其他组件都会被缓存
书写格式与 include 属性一样
<keep-alive exclude="Home"><router-view /></keep-alive>
activated() {}
keep-alive 缓存的组件激活时调用deactivated() {}
keep-alive 缓存的组件失活时调用
activated() {
console.log("进入缓存组件");
},
deactivated() {
console.log("离开缓存组件");
},
编程式路由
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [{
path: '/home',
name: 'Home',
component: Home,
children: [{
path: 'son', // 静态路由
name: 'Son',
component: () => import('../views/Son.vue'),
// 接收 $route 对象作为参数
props(route) {
return {
// 这里通过 query 获取数据
id: route.query.id,
name: route.query.name,
way: route.query.way,
}
}
}]
},
{
path: '/about',
name: 'About',
component: () => import('../views/About.vue')
}
]
const router = new VueRouter({
routes
})
export default router
push & replace
this.$router.push("指定路由地址")
:跳转到指定路由地址
<template>
<div id="app">
<div id="nav">
<router-link to="/home">Home</router-link> |
<router-link :to="{ name: 'About' }">About</router-link> |
<button @click="jump">jump Son.vue</button>
</div>
<router-view />
</div>
</template>
<script>
export default {
name: "App",
methods: {
jump() {
this.$router.push("/home/son");
},
},
};
</script>
- 使用 push 跳转,会有历史记录
- 如果不想产生历史记录,可以使用 replace 代替 push 跳转
this.$router.replace("/myself");
- 字符串形式
// 传递 query 数据
this.$router.push("/home/son?way=push&id=01&name=superman");
// 传递 params 数据
this.$router.push("/home/son/push/01/superman");
- 对象形式
this.$router.push({
name: "Son",
// 传递 params 数据则该属性名为 params
query: {
way: "push",
id: "01",
name: "superman",
},
});
跳转历史记录
this.$router.forward()
前进this.$router.back()
后退this.$router.go(num)
:num > 0,则前进;num < 0,则回退
<template>
<div id="app">
<div id="nav">
<router-link to="/home">Home</router-link> |
<router-link :to="{ name: 'About' }">About</router-link> |
<button @click="jump">jump Son.vue</button> |
<button @click="forward">前进</button> |
<button @click="back">后退</button>
</div>
<router-view />
</div>
</template>
<script>
export default {
name: "App",
methods: {
jump() {
this.$router.replace({
name: "Son",
query: {
way: "push",
id: "01",
name: "superman",
},
});
},
forward() {
this.$router.forward();
},
back() {
this.$router.back();
},
},
};
</script>
导航守卫
vue-router 提供的导航守卫主要用来通过跳转 / 取消的方式守卫导航
全局路由守卫
router.beforeEach((to, from, next) => {})
:全局前置守卫,路由进入前触发- 当一个导航触发时,全局前置守卫按照创建顺序调用
- 每个守卫方法接收 3 个参数:
to
: 即将要进入的目标
from
: 当前导航正要离开的路由
next
:放行函数,不调用则会一直卡在这里,无法执行后面的函数
// 全局前置守卫,任何路由进入之前触发
router.beforeEach((to, from, next) => {
console.log("to", to);
console.log("from", from);
next(); // 放行
});
router.afterEach(to, from)
:全局后置守卫,路由离开后触发- 每个守卫方法接收 2 个参数:
to
: 即将要进入的目标
from
: 当前导航正要离开的路由 - 对于分析、更改页面标题、声明页面等辅助功能以及许多其他事情都很有用
// 全局后置守卫,任何路由离开后触发
router.afterEach((to, from) => {
console.log("to", to);
console.log("from", from);
document.title = to.name; // 修改页面标题
});
指定路由守卫
beforeEnter: (to, from, next) => {}
:指定路由守卫,路由进入前触发- 指定路由守卫只有进入前触发,没有离开后触发
- 直接在路由配置上定义
beforeEnter
守卫:
const routes = [{
path: '/home',
name: 'Home',
component: Home,
children: [{
path: 'son',
name: 'Son',
component: () => import('../views/Son.vue'),
props(route) {
return {
id: route.query.id,
name: route.query.name,
way: route.query.way,
}
},
// 指定路由守卫
beforeEnter: (to, from, next) => {
console.log("to", to);
console.log("from", from);
next(); // 放行
},
}]
}, {
path: '/about',
name: 'About',
component: () => import('../views/About.vue')
}]
组件内的守卫
- 页面路由进入 / 离开,一般在模版中设置:
beforeRouteEnter(to, from, next)
:路由进入前触发beforeRouteLeave(to, from, next)
:路由离开后触发beforeRouteUpdate(to, from, next)
:路由复用时触发
<script>
export default {
name: "Son",
props: ["way", "id", "name"],
beforeRouteEnter(to, from, next) {
console.log("路由进入前触发");
console.log("to", to);
console.log("from", from);
next();
},
beforeRouteLeave(to, from, next) {
console.log("路由离开后触发");
console.log("to", to);
console.log("from", from);
next();
},
beforeRouteUpdate(to, from) {
console.log("路由组件复用时触发");
console.log("to", to);
console.log("from", from);
},
};
</script>
beforeRouteEnter
方法中,无法直接获取 this 对象
beforeRouteEnter(to, from, next) {
console.log("路由进入之前触发", this); // 这里的 this 为 undefined
next((vm) => { // 但是我们可以通过 next 的回调函数获取
console.log("vm", vm);
});
},
完整的导航解析流程
- 导航被触发
- 在失活的组件里调用
beforeRouteLeave
守卫 - 调用全局的
beforeEach
守卫 - 在重用的组件里调用
beforeRouteUpdate
守卫 - 在路由配置里调用
beforeEnter
- 解析异步路由组件
- 在被激活的组件里调用
beforeRouteEnter
- 调用全局的
beforeResolve
守卫 - 导航被确认
- 调用全局的
afterEach
钩子 - 触发 DOM 更新
- 调用
beforeRouteEnter
守卫中传给next
的回调函数,创建好的组件实例会作为回调函数的参数传入
路由元信息
- 用于将任意信息附加到路由上,如过渡名称、谁可以访问路由等
- 这些事情可以通过接收属性对象的
meta
属性来实现,并且它可以在路由地址和导航守卫上都被访问到
定义路由的时候你可以这样配置 meta
字段:
const routes = [{
path: '/home',
name: 'Home',
component: Home,
meta: { // 设置 meta 属性
name: 'myHome'
},
children: [{
path: 'son',
name: 'Son',
component: () => import('../views/Son.vue'),
props(route) {
return {
id: route.query.id,
name: route.query.name,
way: route.query.way,
}
},
meta: { // 设置 meta 属性
name: 'mySon'
},
}]
}, {
path: '/about',
name: 'About',
component: () => import('../views/About.vue'),
meta: { // 设置 meta 属性
name: 'myAbout'
},
}]
const router = new VueRouter({
routes
})
// 全局后置守卫,任何路由离开后触发
router.afterEach((to, from) => {
document.title = to.meta.name; // 获取 meta 中的数据
});
mode
mode
:路由模式 abstract、hash(默认)、history
hash
: 使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History Api 的浏览器history
: 依赖 HTML5 History API 和服务器配置abstract
: 支持 JS 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式
const router = new VueRouter({
mode: "abstract",
routes
})
mode: 'history'
配置已经被一个更灵活的 history
配置所取代。根据你使用的模式,你必须用适当的函数替换它:
"history"
:createWebHistory()
"hash"
:createWebHashHistory()
"abstract"
:createMemoryHistory()
下面是一个完整的代码段:
import { createRouter, createWebHistory } from 'vue-router'
// 还有 createWebHashHistory 和 createMemoryHistory
createRouter({
history: createWebHistory(),
routes: [],
})