Vue 路由
基本使用
安装
npm i vue-router@3
配置
- 在 src 目录下生成 router/index.js 用于存放 router 的配置。
router/index.js
// 引入 vue-router
import VueRouter from "vue-router"
// 引入相关组件
import About from "../components/About.vue"
import Home from "../components/Home.vue"
export default new VueRouter({
routes: [
{ path: "/", redirect: "/about" },
{ path: "/about", component: About },
{ path: "/home", component: Home }
]
})
- 在 main.js 中引入 router。
// import Vue from "vue"
import Vue from "vue"
import App from "./App.vue"
// 引入 vue-router
import VueRouter from "vue-router"
// 引入配置好的 router
import router from "./router"
// 关闭生产提示
Vue.config.productionTip = false
// 使用插件
Vue.use(VueRouter)
new Vue({
render: (h) => h(App),
// 添加 router
router
}).$mount("#app")
使用
在 router 的配置文件中设置组件的路由配置。
设置好后,在组件中将需要跳转的 a 链接使用 vue-router 的 router-link 代替。然后再需要显示组件视图的地方使用 router-veiw 标签。
以下实例使用了 bootstrap 3.3.5
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" />
App.vue
<template>
<div id="app">
<div class="row">
<div class="col-xs-offset-2 col-xs-8">
<div class="page-header"><h2>Vue Router Demo</h2></div>
</div>
</div>
<div class="row">
<div class="col-xs-2 col-xs-offset-2">
<div class="list-group">
<!-- <a class="list-group-item active" href="./about.html">About</a>
<a class="list-group-item" href="./home.html">Home</a> -->
<router-link class="list-group-item" active-class="active" to="/about">About</router-link>
<router-link class="list-group-item" active-class="active" to="/home">Home</router-link>
</div>
</div>
<div class="col-xs-6">
<div class="panel">
<div class="panel-body">
<router-view></router-view>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "App"
}
</script>
<router-link class="list-group-item" active-class="active" to="/about">About</router-link>
用于代替 a 链接。- 其中
active-class="active"
表示,处于激活选中的链接使用.active
样式。 to="/about"
表示点击后需要路由到的地址。其中的/
要注意携带。
- 其中
<router-view></router-view>
用于展示路由之后的组件。组件将显示在此处。- 使用 router 来调用组件时不需要手动引入组件,并再 components 中配置。因为再路由的配置文件 index.js 中已经引入过相关组件了。
深入解析
路由组件与一般组件
由路由调用显示的组件我们叫做路由组件,一般放置于 pages 文件夹。
正常引入由标签手动调用的组件叫做一般组件,一般放置于 components 文件夹。
组件切换
组件进行切换时,其他组件就被 router 销毁了。之后我们可以使用 keep-alive 来让其不销毁。
我们通过操作钩子就可以轻松验证。
Home.vue
<template>
<h2>我是Home的内容</h2>
</template>
<script>
export default {
name:"Home",
mounted() {
console.log("home 组件被挂载");
},
destroyed() {
console.log("home 组件被销毁");
},
}
</script>
vc 实例对象分析
我们使用了 vue-router 之后,我们的 vc 实例对象自身就多了 $route 和 $router 两个属性。
$route 里面存储着路由规则。里面存储了当前组件的一些路由信息,是当前组件独有的。
$router 是整个应用的路由器,整个应用只有一个,所以不同 vc 身上的 $router 都是同一个路由器。
嵌套路由
嵌套路由需要在路由的配置中,在需要嵌套的对象身上继续套娃添加 children 属性,以数组形式嵌套。
router/index.js
// 引入 vue-router
import VueRouter from "vue-router"
// 引入相关组件
import About from "../pages/About.vue"
import Home from "../pages/Home.vue"
import News from "../pages/News.vue"
import Message from "../pages/Message.vue"
export default new VueRouter({
routes: [
{ path: "/", redirect: "/about" },
{ path: "/about", component: About },
{
path: "/home",
component: Home,
children: [
{ path: "", redirect: "news" },
{ path: "news", component: News },
{ path: "message", component: Message }
]
}
]
})
- 在嵌套路由 children 中配置 path 时,router 会自动为我们加上
/
所以不需要我们去手动添加,如果添加后,如path="/news"
,那么进行路由到组件的时候识别的是//news
,就不会将 news 组件显示出来。 - 一般来说可以多层嵌套,但是实际中嵌套到五六层就已经很少见了。
home.vue
<template>
<div>
<h2>我是Home的内容</h2>
<ul class="nav nav-tabs">
<li>
<!-- <a class="list-group-item" href="./home-news.html">News</a> -->
<router-link
class="list-group-item"
active-class="active"
to="./news"
>News</router-link
>
</li>
<li>
<router-link
class="list-group-item"
active-class="active"
to="/home/message"
>Message</router-link
>
</li>
</ul>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "Home"
}
</script>
- 嵌套路由在组件中使用方法和一层的时候类似。
- 对于 to 属性,我们在这里需要将完整地址写上如
to="/home/message"
,因为再配置文件中是嵌套配置的,需要先去找外层的 home,然后再去找内层 message,所以这里的 to 要写全。也可以使用./
简写,如to="./message"
,这里的./
就已经代表了/home
,所以也是写全了的。
命名路由
为了规避过长的 path 产生的不便,router 允许我们为路由进行命名。
这里仅部分举例 index.js
export default new VueRouter({
routes: [
{ path: "/", redirect: "/about" },
{ name: "guanyu", path: "/about", component: About },
{
name: "jia",
path: "/home",
component: Home,
children: [
{ path: "", redirect: "news" },
{ name: "xinwen", path: "news", component: News },
{ name: "xinxi", path: "message", component: Message }
]
}
]
})
在 Home.vue 中使用
<router-link
class="list-group-item"
active-class="active"
:to="{
name:'xinwen'
}"
>News</router-link>
- 如果使用 name 进行跳转时,需要给 to 传递一个包含 name 属性的对象。注意需要使用
:to
,因为这样传递的才是表达式,否则全部视为字符串。 - 这里的
:to="{name:'xinwen'}"
与to="/home/news"
和:to="{path:'/home/news'}"
等价。
路由间传递值
在使用 vue-router 之后,vc 上会存在 $route 属性,来携带自身的路由信息。其中就有专门用于传递值的 query 和 params。
使用 query 传值
query 传值可以直接写在 router-link 标签的 to 上,也可以以对象的形式赋值给 to。
Message.vue
<template>
<div>
<ul>
<li v-for="m in messageList" :key="m.id">
<!-- <a href="/message1">{{ m.title }}</a> -->
<!-- <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{
m.title
}}</router-link> -->
<router-link
:to="{
path: '/home/message/detail',
query: { id: m.id, title: m.title }
}"
>{{ m.title }}</router-link
>
</li>
</ul>
<hr />
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "Message",
data() {
return {
messageList: [
{ id: "001", title: "消息001" },
{ id: "002", title: "消息002" },
{ id: "003", title: "消息003" }
]
}
}
}
</script>
-
如果需要在字符串中传递变量可以使用模板字符串,在需要的位置使用
${变量}
直接进行拼接。 -
:to="`/home/message/detail?id=${m.id}&title=${m.title}`" :to="{ path: '/home/message/detail', query: { id: m.id, title: m.title } }" 上面二者等价
使用 params 传值
使用 params 传值,需要在路由配置中配置需要传递的值。
index.js
export default new VueRouter({
routes: [
{ path: "/", redirect: "/about" },
{ name: "guanyu", path: "/about", component: About },
{
name: "jia",
path: "/home",
component: Home,
children: [
{ path: "", redirect: "news" },
{ name: "xinwen", path: "news", component: News },
{
name: "xinxi",
path: "message",
component: Message,
children: [
// 需要在此配置 params 需要传递的值
{ name: "xiangqing", path: "detail/:id/:title", component: Detail }
]
}
]
}
]
})
Message.vue
<template>
<div>
<ul>
<li v-for="m in messageList" :key="m.id">
<!-- <a href="/message1">{{ m.title }}</a> -->
<!-- <router-link :to="`/home/message/detail/${m.id}/${m.title}`">{{
m.title
}}</router-link> -->
<router-link
:to="{
name: 'xiangqing',
params: { id: m.id, title: m.title }
}"
>{{ m.title }}</router-link
>
</li>
</ul>
<hr />
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "Message",
data() {
return {
messageList: [
{ id: "001", title: "消息001" },
{ id: "002", title: "消息002" },
{ id: "003", title: "消息003" }
]
}
}
}
</script>
- 如果不适用对象形式给 to 传递,直接以 /xxx/xxx 在地址后拼接需要传递的值。
- 如果使用 to 的对象形式,那么使用 params 传递值时,不能使用 path 指定路由地址,必须使用 name 来指定路由地址。
路由的 props 配置
index.js 部分
export default new VueRouter({
routes: [
{ path: "/", redirect: "/about" },
{ name: "guanyu", path: "/about", component: About },
{
path: "/home",
component: Home,
children: [
{ path: "", redirect: "news" },
{ name: "xinwen", path: "news", component: News },
{
name: "xinxi",
path: "message",
component: Message,
children: [
{
name: "xiangqing",
path: "detail/:id/:title",
component: Detail,
// 第一种 props 写法,在配置中直接传递确定值,不需要在 path 配置中占位
// props: { a: 1, b: 2 }
// 第二中 props 写法,默认将组件中 to 的 params 以 props 形式传递给子组件, 不会理会 query, 需要在 path 配置中占位
// props: true
// 第三种 既可以传递 query 也可以传递 params, 如果是 params 需要在 path 配置中占位
// props($route) {
// return {
// id: $route.params.id,
// title: $route.params.title
// }
// }
// 解构赋值
props({ params: { id, title } }) {
return {
id,
title
}
}
// props({ query: { id, title } }) {
// return {
// id,
// title
// }
// }
}
]
}
]
}
]
})
Message.vue
<template>
<div>
<ul>
<li v-for="m in messageList" :key="m.id">
<!-- <a href="/message1">{{ m.title }}</a> -->
<!-- <router-link :to="`/home/message/detail/${m.id}/${m.title}`">{{
m.title
}}</router-link> -->
<router-link
:to="{
name: 'xiangqing',
params: { id: m.id, title: m.title }
}"
>{{ m.title }}</router-link
>
</li>
</ul>
<hr />
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "Message",
data() {
return {
messageList: [
{ id: "001", title: "消息001" },
{ id: "002", title: "消息002" },
{ id: "003", title: "消息003" }
]
}
}
}
</script>
Detail.vue
<template>
<ul>
<li>消息编号:{{ id }}</li>
<li>消息标题:{{ title }}</li>
<!-- <p>a: {{ a }}, b: {{ b }}</p> -->
</ul>
</template>
<script>
export default {
name: "Detail",
props: ["id", "title"]
}
</script>
- 使用 props 传值,不管是哪种,都需要在子组件中使用 props 接收。
编程式路由导航
路由历史记录
push
在浏览器进行路由跳转时,浏览器保留每一条历史记录,返回时按跳转顺序返回。
使用 router-link 进行跳转时,默认使用的就是使用的 push,可以在标签属性中添加 replace 来指定使用 replace。如<router-link replace></router-link>
replace
在浏览器进行路由跳转时,替换当前的历史记录。比如从 /home
,跳转到/home/news
时,会将/home
历史记录替换掉。
编程式路由跳转
编程式路由跳转就是用 js 代码进行跳转。
跳转时我们用的是路由器 $router。
Message.vue
<template>
<div>
<ul>
<li v-for="m in messageList" :key="m.id">
<!-- <a href="/message1">{{ m.title }}</a> -->
<!-- <router-link :to="`/home/message/detail/${m.id}/${m.title}`">{{
m.title
}}</router-link> -->
<router-link
:to="{
name: 'xiangqing',
params: { id: m.id, title: m.title }
}"
>{{ m.title }}</router-link
>
<button @click="pushShow(m)">push</button>
<button @click="replaceShow(m)">replace</button>
</li>
</ul>
<hr />
<router-view></router-view>
</div>
</template>
<script>
export default {
name: "Message",
data() {
return {
messageList: [
{ id: "001", title: "消息001" },
{ id: "002", title: "消息002" },
{ id: "003", title: "消息003" }
]
}
},
methods: {
pushShow(m) {
this.$router.push({
name: "xiangqing",
params: { id: m.id, title: m.title }
})
},
replaceShow(m) {
this.$router.replace({
name: "xiangqing",
params: { id: m.id, title: m.title }
})
},
back() {
this.$router.back()
},
forward() {
this.$router.forward()
}
}
}
</script>
$router.push()
保留历史路由跳转。$router.replace()
替换上一个历史路由跳转。$router.back()
后退到上一个页面历史。$router.forward()
前进到下一个页面历史。
缓存路由组件
我们进行路由跳转时,有时需要保留数据。但是路由跳转后默认会销毁组件。这时就需要用到我们的缓存路由组件了。
缓存路由组件只需要在 router-view 外层包裹 keep-alive 标签即可
<template>
<div>
<h2>我是Home的内容</h2>
<ul class="nav nav-tabs">
<li>
<!-- <a class="list-group-item" href="./home-news.html">News</a> -->
<router-link class="list-group-item" active-class="active" to="./news"
>News</router-link
>
</li>
<li>
<router-link
class="list-group-item"
active-class="active"
to="/home/message"
>Message</router-link
>
</li>
</ul>
// 缓存路由组件,include指定被缓存的组件名。
<keep-alive include="News">
<router-view></router-view>
</keep-alive>
</div>
</template>
<script>
export default {
name: "Home",
destroyed() {
console.log("home 组件被销毁")
}
}
</script>
- 如果 keep-alive 不指定 include 组件,那么所有路由组件都将进行缓存。如果指定了只有指定组件进行缓存。
- 在 include 中指定的是组件的名字。
- 指定多个组件需要用数组的形式如:
<keep-alive :include="['News','Message']">
<router-view></router-view>
</keep-alive>
新增钩子
activated(){}
:组件被激活时调用。deactivated(){}
:组件失活时调用。- 另一个声明周期图外的钩子是
$nextTick(() => {})
,作用是等当前组件渲染完毕再调用里面的代码。
路由守卫
再路由进行跳转时,守卫可以为我们再路由跳转前或路由跳转后做相应的逻辑判断。
全局前置守卫
全局前置守卫声明再路由的配置文件中,当初始化的时候被调用,每次路由切换之前也被调用。
const router = new VueRouter({
routes: [...]
})
// 全局前置守卫
router.beforeEach((to, from, next) => {
if (to.path === "/about") {
alert("不允许访问about页面")
} else {
next()
}
})
export default router
- 需要再暴露之前设置全局前置守卫。
全局后置守卫
// 引入 vue-router
import VueRouter from "vue-router"
// 引入相关组件
import About from "../pages/About.vue"
import Home from "../pages/Home.vue"
import News from "../pages/News.vue"
import Message from "../pages/Message.vue"
import Detail from "../pages/Detail.vue"
const router = new VueRouter({
routes: [
{ path: "/", redirect: "/about" },
{
name: "guanyu",
path: "/about",
component: About,
meta: { title: "About" }
},
{
path: "/home",
component: Home,
meta: { title: "Home" },
children: [
{ path: "", redirect: "news" },
{
name: "xinwen",
path: "news",
component: News,
meta: { title: "News" }
},
{
name: "xinxi",
path: "message",
component: Message,
meta: { title: "Message" },
children: [
{
name: "xiangqing",
path: "detail/:id/:title",
component: Detail,
// 解构赋值
props({ params: { id, title } }) {
return {
id,
title
}
}
}
]
}
]
}
]
})
// 全局前置守卫
router.beforeEach((to, from, next) => {
if (to.path === "/about") {
alert("不允许访问about页面")
} else {
next()
}
})
// 全局后置守卫
router.afterEach((to, from) => {
// 更改标题
document.title = to.meta.title || "路由demo"
})
export default router
独享路由守卫
独享前置守卫需要写在路由配置中
{
name: "xinwen",
path: "news",
component: News,
meta: { title: "News" },
// 独享前置守卫
beforeEnter: (to, from, next) => {
let pass = prompt("请输入2查看")
if (pass == 2) next()
}
}
组件内路由守卫
- beforeRouteEnter(to, from, next){}:通过路由规则,进入该组件前被调用。有人来到这个组件时调用。next 允许进入。
- beforeRouteLeave(to, from, next){}:通过路由规则,离开该组件时被调用。有人离开这个组件时调用。next 允许离开
<template>
<h2>我是About的内容</h2>
</template>
<script>
export default {
name: "About",
beforeRouteEnter(to, from, next) {
if (prompt("输入2进入about") == 2) next()
},
beforeRouteLeave(to, from, next) {
if (prompt("输入3离开about") == 3) next()
}
}
</script>
- 由于都是进入这个组件,所以 to.path 永远都是当前组件的路由地址。
history 模式与 hash 模式
hash 模式(哈希)
在 http 地址中 # 后面的值全都是哈希值,请求时并不会作为路径的一部传递给服务器。
history 模式(工厂)
在 http 中没有 #。
区别
- hash 模式的兼容性更好,history 模式兼容性略差。
- history 模式当项目部署到服务器时,刷新页面时会出错。
history 模式的问题
当我们打包后,将项目部署到服务器。如果我们在页面内实现跳转时,是 vue 的路由器帮我们跳转查找面,并不是服务器帮我们查找。路由器查找的同时,将我们的 url 替换成查找地址。比如localhost:8888/home/message
。
但是这时我们进行页面刷新时,并不是 vue 的路由器帮我们查找页面,我们会直接把 url 中的内容发送给服务器,进行请求。但是由于我们时单页面应用,并没有相应的文件。所以就会出现报错。
解决 history 模式问题
- 我们使用 hash 模式时可以解决此问题,因为 # 后面的东西不会发送给服务器。
- 我们在后端中使用响应的中间件来解决此问题。在 node.js 中我们可以使用 connect-history-api-fallback 中间件来解决此问题。
在 router 修改路由模式
export default new VueRouter({
mode:"history",
routes: [
{ path: "/", redirect: "/about" },
{ name: "guanyu", path: "/about", component: About },
{
name: "jia",
path: "/home",
component: Home,
children: [
{ path: "", redirect: "news" },
{ name: "xinwen", path: "news", component: News },
{
name: "xinxi",
path: "message",
component: Message,
children: [
// 需要在此配置 params 需要传递的值
{ name: "xiangqing", path: "detail/:id/:title", component: Detail }
]
}
]
}
]
})
- 路由模式默认为 hash 模式。