路由的基本使用
- 下载
npm install vue-router
oryarn add vue-router
- 引入
import VueRouter from 'vue-router'
import router from './router'
- 应用
Vue.use(VueRouter)
- 配置
new Vue({ render: h =>h(App), router }).$mount('#app')
- 配置 Router 文件
// router/index.js 用于创建管理整个应用的路由器 import VueRouter from 'vue-router' // 引入组件 import About from '../components/About.vue' import Home from '../components/Home.vue' // 创建一个路由器 export default new VueRouter({ routes: [ { // 一级路由 name: 'About', path: '/about', component: About }, { // 以及路由 name: 'Home', path: '/home', component: Home }, ] })
- 模板
<!-- 切换按钮,最终会转换成 <a> 标签 --> <!-- active-class 属性为选中后的 css 样式 --> <router-link to="/about" active-class="select">About</router-link> <router-link to="/link" active-class="select">Home</router-link> <!-- 展示区 --> <router-view></router-view>
几个注意点
- 通过路由渲染的组件叫路由组件,反之(通过组件标签渲染)为普通组件,路由组件最好放在单独的文件夹里统一管理,比如
pages
文件夹 - 当前未展示的路由组件,默认是被销毁的,当需要渲染时,再重新挂载
- 参与了路由配置的路由组件,可以调用
$route
和$router
。$route
为当前组件的路由配置信息,$router
为全局路由器
嵌套路由
- 一级路由里面可以配置二级路由,同理,二级路由下面可以配置三级路由,以此类推
... import News from '../pages/News.vue' import Message from '../pages/Message.vue' export default new VueRouter( route:[ ... { name:'Home', path: '/home', component: Home, // 二级路由 children: [ { name: 'News', path: 'news', component: News }, { name: 'Message', path: 'message', component: Message } ] } ] )
<!-- Home 组件 --> <template> <router-link active-class="select" to="/home/news">news</router-link> <router-link active-class="select" to="/home/message">message</router-link> <router-view><router-view> </template>
路由传参 路由的 query 参数
- 从三级路由组件 Detail 中展示二级路由组件 Message 的详细信息
- 路由配置
import Detail from '../pages/Detail.vue' ... { // 二级路由 name: 'Message', path: 'message', component: Message children: [ { // 三级路由 name: 'Detail', path: 'detail', component: Detail } ] } ...
- Message 路由组件中传递 query 参数的两种写法
<template> <div> <ul> <li v-for="m in messageList" :key="m.id"> <!-- 传递 query 参数,字符串写法 --> <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">消息1</router-link> <!-- or --> <!-- 传递 query 参数,对象写法 --> <router-link :to="{ path:'/home/message/detail', query:{ id: m.id, title: m.title } }">消息1</router-link> </li> </ul> </div> </template>
<script> export default { name: 'Message', data(){ return { messageList:[ {id:'001',title:'详情001'}, {id:'002',title:'详情002'}, {id:'003',title:'详情003'} ] } } } </script>
- Detail 路由组件通过 $route 接收 query 参数
<template> <ul> <li>消息编号: {{$route.query.id}}</li> <li>消息标题: {{$route.query.title}}</li> </ul> </template>
命名路由
- 当路由出现多级嵌套时,嵌套层级越多,子级路由的路径 path 就会变的越长
- 当路由配置了
name
属性时,在多级路由里就可以直接使用 name 来代替 path 指定渲染的目标 - 如上代码
... <!-- 传递 query 参数,对象写法 --> <router-link :to="{ name: 'Detail', query:{ id: m.id, title: m.title } }">消息1</router-link> ...
路由的 params 参数
- 要使用 params 传递参数,就要在路由器的配置里使用占位符,来告诉路由器,携带参数的位置
import Detail from '../pages/Detail.vue' ... { // 二级路由 name: 'Message', path: 'message', component: Message children: [ { // 三级路由 name: 'Detail', path: 'detail/:id/:title', // 使用占位符 component: Detail } ] } ...
- 模板中携带参数时,直接拼接到 path 后面
- 如果使用对象写法,指定目标必须用
name
<!-- 传递 params 参数,字符串写法 --> <router-link :to="`/home/message/detail/${m.id}/${m.title}`">消息1</router-link> <!-- or --> <!-- 传递 params 参数,对象写法 --> <router-link :to="{ name:'Detail', params:{ id: m.id, title: m.title } }">消息1</router-link>
- Detail 路由组件中获取 params 参数
<template> <ul> <li>消息编号:{{$route.params.id}}</li> <li>消息标题: {{$route.parmas.title}}</li> </ul> </template>
路由的 props 配置
- props 可以解决路由组件中接收 params 和 query 参数时,由于参数过多造成的代码重复的问题
- props 配置有三种写法
import Detail from '../pages/Detail.vue' ... { // 二级路由 name: 'Message', path: 'message', component: Message children: [ { // 三级路由 name: 'Detail', path: 'detail/:id/:title', // 使用占位符 component: Detail // 对象写法 props: {a: 1, b: 2} // 布尔写法 props: true // 函数写法 props($route){ return {id: $route.query.id, title: $route.query.title} } } ] } ...
- 对象写法:该对象中所有的 key、value 都会以
props
的形式传递给 Detail 组件 - 布尔写法:若布尔值为真,就会将该路由组件收到的所有
params
参数,传给 Detail 组件,注意不会
传递query
参数 - 函数写法:vue-router 会将
$route
以参数的形式传递给函数,在函数中,可以借助$route
获取任意参数,并通过返回值以props
的形式传递给 Detail 组件 - 最终, Detail 组件通过 props 接收参数后的代码如下
<template> <ul> <li>消息编号:{{id}}</li> <li>消息标题: {{title}}</li> </ul> </template> <script> export default { name: 'Detail', props: ['id','title'] } </script>
router-link 的 replace 属性
- router-link 标签的每次点击都会形成历史记录,默认为
push
模式 - 对历史记录的操作模式除了
push
,还有replace
模式,它与 push 模式最大的不同就是 replace 会替换掉当前栈顶的那条记录 - 开启 replace 模式:
<router-link :replace="true" to="/xx">...</router-link> <!-- or --> <router-link replace to="/xx">...</router-link>
编程式路由导航
-
router-link 标签在模板渲染后最终会成为 a 标签,当有特殊需求不能使用 a 标签时,就需要使用其他方案代替 router-link
-
使用 button 标签实现路由跳转:
<template> ... <li v-for="m in messageList" :key="m.id"> ... <button @click="pushShow(m)">push查看</button> <button @click="replaceShow(m)">replace查看</button> </i> ... <router-view></router-view> </template>
... methods:{ pushShow(m){ this.$router.push({ name:'Detail', params: { id: m.id, title: m.title } }) }, replaceShow(m){ this.$router.replace({ name:'Detail', params: { id: m.id, title: m.title } }) } } ...
-
除了 button,任何一个可以绑定并触发事件的元素,都可以实现编程式路由
缓存路由组件
- 当路由从当前渲染组件切到其他路由组件时,当前被切出的路由组件将会被销毁。也就是说,即将被销毁的路由组件,无法保存用户所输入的任何内容。
- 使用
keep-alive
标签,将不希望被销毁的路由组件的展示区(router-view)包裹起来,可以实现路由组件不会被销毁<template> ... <router-link to="...">...</router-link> <!-- 如果不配置 include 那么标签内的所有路由组件都将被缓存 --> <!-- 如果需要配置多个组件名,那么使用数组 :nclude="['名1','名2']" --> <keep-alive include="组件名"> <router-view></router-view> </keep-alive> ... </template>
路由组件独有的生命周期钩子,用于捕获路由组件的激活状态
- activated
- 当前路由组件激活(渲染)时触发
- deactivated
- 当前路由组件失活(被跳转)时触发
路由守卫
全局前置路由守卫
- beforeEach
- 全局前置路由守卫,在第一次初始化和每一次路由切换之前被调用
- 三个参数, to 为目标组件,from 为起始组件, next 为放行函数
... const router = new VueRouter({ routes:[ { name: 'News', path: '/news', component: News }, { name: 'Message', path: '/message', component: Message } ] }) router.beforeEach(()=>{ if (to.name === 'News' || to.name === 'Message') { if (localStorage.getItem('myId') === 'ok') { next() } else { alert('用户名或者密码错误,登录失败') } } else { next() } }) export default router
- 利用 name 或者 path 来判断路由是否被校验时,如果需要被校验的路由过多,那代码就会变得繁琐,我们可以在需要被校验的路由里的路由元信息
meta
里配置一个布尔值,通过布尔值来判断... const router = new VueRouter({ routes:[ { name: 'News', path: '/news', component: News, meta:{isAuth:true} }, { name: 'Message', path: '/message', component: Message, // 如果不需要校验,也可以不写,因为 null 也是false meta:{isAuth: false} } ] }) router.beforeEach(()=>{ if (to.mata.isAuth){ if (localStorage.getItem('myId') === 'ok') { next() } else { alert('用户名或者密码错误,登录失败') } } else { next() } }) export default router
全局后置路由守卫
- router.afterEach
- 两个参数 to、 from
- 常用来自动切换页面的 title
... router.afterEach((to, from) => { document.title = to.name })
独享路由守卫
- beforeEnter
- 顾名思义,就是单独在指定的路由内配置的路由守卫
... export default new VueRouter({ routes:[ { name: 'News', path: '/news', component: News, beforeEnter:(to,from,next)=>{ if (localStorage.getItem('myId') === 'ok') { next() } else { alert('用户名或者密码错误,登录失败') } } }, { name: 'Message', path: '/message', component: Message, } ] })
组件内路由守卫
- beforeRouteEnter(to, from, next){}
- 通过路由规则进入该组件时被调用
- beforeRouteLeave(to, from, next){}
- 通过路由规则离开该组件时被调用
路由器的两种基本工作模式
哈希模式
- vue-router 默认开启的是
hash
模式,当使用哈希模式时,浏览器的路径里会自动携带一个#
,#
后面的内容不会通过 http 请求发送给服务器
history 模式
- 要开启路由器的 history 模式,需要在创建路由器时,配置
mode
... export default new VueRouter({ mode: 'history', routes:[{...}] })
- history 的兼容性略差
- history 模式下,路径不会携带
#
,在项目上线之前,这一点尤为不同。由于 history 模式不会携带#
,所有由前端路由产生的路径在页面刷新时,就会被浏览器当作服务器的资源发起 http 请求,如果服务器没有这些资源,就会报错- 有多种解决办法,比如后端将前端路由产生的路径在服务器端进行比对,比如 node.js 的
connect-history-api-fallback
中间件,就是专门为了解决这个问题的
- 有多种解决办法,比如后端将前端路由产生的路径在服务器端进行比对,比如 node.js 的
- hash 模式在通过第三方手机 app 分享时,若 app 校验严格则地址会因为
#
而被校验为不合法,history 模式就避免了这个问题