目录
一、初识路由
1.1 什么是 Vue-Router
1.2 什么单页面应用
1.3 Vue-Router的使用步骤
1.4 什么是router-link?
1.5 创建路由的几种方式
1.6 组件可以分为哪两类
1.7 路由综合小案例
二、声明式导航
2.1 声明式导航基础使用
2.2 声明式导航跳转传值
三、重定向和别名
3.1 重定向
3.2 给路由起别名
四、路由 404 设置
五、模式修改(History模式)
六、编程式导航
6.1 编程式导航基本使用
6.2 编程式导航传参
七、嵌套路由
八、命名视图
九、导航守卫
9.1 全局前置守卫(beforeEach)--跳转之前触发
9.2 全局解析守卫(beforeResolve)
9.3 全局后置钩子(afterEach)--跳转之后触发
9.4 路由独享的守卫(beforeEnter)
9.5 组件内的守卫
9.6 完整的导航解析流程
十、路由元信息
实现业务:
十一、导航高亮
11.1 初识导航高亮
11.2 示例
路由是什么?
一种映射关系
Vue 中的路由是什么?
单页面应用(SPA): 所有功能在同一个html页面上实现,网页不刷新
前端路由作用: 实现业务场景切换
优点:
缺点:
单页面如何切换场景:依赖路由切换显示
通过a标签确实能够设置URL的hash,但是这种方式并不专业,在Vue-Router中专门提供了一个专门用于设置hash的标签-router-link
特点:
默认情况下Vue会将router-link渲染成a标签,但是我们可以通过tag来指定到底渲染成什么
给router-link设置选中样式:
默认情况下我们可以通过重写 router-link-active 类名来实现选中样式,但是我们也可以通过linkActiveClass来指定选中样式
重定向路由:
{path: '被重定向值',redirect: '重定向之后的值'}
注意点:
如果是通过router-link的方式来设置URL的hash值,那么不用写#,而是通过to属性来设置hansh值
a.直接下载 / CDN
切换到第一个界面
切换到第二个界面
我是第一个界面
我是第二个界面
b.NPM--非手动下载
npm install vue-router --save
如果在一个模块化工程中使用它,必须要通过 Vue.use()
明确地安装路由功能:
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
如果使用全局的 script 标签,则无须如此 (手动安装),如上面的 a
使用 vue create xxx 创建 vue 项目
在src目录下新建router文件夹和views文件夹,然后分别新建index.js和About.vue、Home.vue文件
项目结构如下:
示例:
Home.vue
首页
About.vue
关于页面
router/index.js
/**
* 1.
*/
import Vue from 'vue'
import VueRouter from 'vue-router'
/**
* 2.
*/
import Home from "../views/Home.vue"
import About from "../views/About.vue"
Vue.use(VueRouter)
/**
* 3.
*/
const routes = [{
path: "/",
component: Home
},
{
path: "/about",
component: () =>
import ("../views/About.vue")
}
]
/**
* 4. 创建 router 实例,然后传 `routes` 配置
* 还可以传别的配置参数
*/
const router = new VueRouter({
routes // (缩写) 相当于 routes: routes
});
export default router
main.js
import Vue from 'vue'
import App from './App.vue'
import './registerServiceWorker'
import router from "./router"
Vue.config.productionTip = false
/**
* 5.创建和挂载根实例--router
*/
new Vue({
router,
render: h => h(App),
}).$mount('#app')
App.vue
Home |
About
c.Vue CLI
如果你有一个正在使用 Vue CLI的项目,你可以以项目插件的形式添加 Vue Router。CLI 可以生成上述代码及两个示例路由。它也会覆盖你的 App.vue
,因此请确保在项目中运行以下命令之前备份这个文件:
vue add router
使用 vue create xxx 创建 vue 项目
然后在项目根目录下通过 vue add router 来进行安装路由:
安装完成后会发现在src目录下自动新增了router和views两个文件夹以及分别多了index.js和About.vue、Home.vue文件:
会发现router文件夹和views文件夹里面的文件的内容和上面手动创建的router文件夹和views文件夹里面的文件的内容差不多,最后运行效果如下:
vue creat xxx 创建vue项目时勾选上路由演示:
创建完成后,项目结构如下:
会发现在src目录下自动新增了router和views两个文件夹以及分别多了index.js和About.vue、Home.vue文件
router文件夹和views文件夹里面的文件的内容和上面手动创建的router文件夹和views文件夹里面的文件的内容差不多,最后运行效果如下:
d.构建开发版
如果你想使用最新的开发版,就得从 GitHub 上直接 clone,然后自己 build 一个 vue-router
。
git clone https://github.com/vuejs/vue-router.git node_modules/vue-router
cd node_modules/vue-router
npm install
npm run build
.vue文件本质无区别, 方便大家学习和理解, 总结的一个经验
src/views文件夹:页面组件 - 页面展示 - 配合路由用
src/components文件夹:复用组件 - 展示数据/常用于复用
前期准备:
通过 vue create xxx 创建vue项目,这里在创建时我们勾选上 router 选项,自动创建路由
将前面学习 axios 时封装的跨域配置,utils文件夹以及 vue.config.js文件分别放置在该项目的src目录和根目录下
通过 npm install axios --save 安装 axios
项目结构:
vue.config.js
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://iwenwiki.com',
changeOrigin: true,
pathRewrite: { // 路径重写
"^/api": ""
}
}
}
}
}
src/utils/request.js
import axios from "axios"
import qs from "querystring"
/**
* 处理错误信息
* status:状态吗
* info:具体信息
*/
const errorHandle = (status,info) =>{
switch(status){
case 400:
console.log("语义错误");
break;
case 401:
console.log("服务器认证失败");
break;
case 403:
console.log("服务器请求拒绝执行");
break;
case 404:
console.log("请检查网路请求地址");
break;
case 500:
console.log("服务器发生意外");
break;
case 502:
console.log("服务器无响应");
break;
default:
console.log(info);
break;
}
}
/**
* 创建Axios对象
*/
const instance = axios.create({
// 公共配置
// baseURL:"http://iwenwiki.com",
timeout:5000,
// withCredentials: true
})
/**
* 拦截器
*/
instance.interceptors.request.use(
config =>{
if(config.method === 'post'){
// token:登陆信息凭证
config.data = qs.stringify(config.data)
}
return config
},
error => Promise.reject(error)
)
instance.interceptors.response.use(
// 完成了
response => response.status === 200 ? Promise.resolve(response) : Promise.reject(response),
error =>{
// 错误信息的处理
const { response } = error;
if(response){
errorHandle(response.status,response.info)
}else{
console.log("网络请求被中断了");
}
}
)
// get和post等请求方案
export default instance
App.vue
src/views/Home.vue
src/views/About.vue
This is an about page
src/views/NotFound.vue
404
src/views/Details.vue
详情页
id={{$route.params.id}}
{{detailsData.desc}}
components/ListView.vue
-
{{item.title}}
{{item.username}}
src/api/index.js
import axios from "../utils/request"
const base = {
baseUrl: '/api',
list: '/api/FingerUnion/list.php',
details: '/api/FingerUnion/details.php'
};
const api = {
/**
* 获得 list 数据
*/
getList() {
return axios.get(base.baseUrl + base.list);
},
getDetails(params) {
return axios.get(base.baseUrl + base.details, {
params
});
}
}
export default api;
src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: () =>
import ( /* webpackChunkName: "about" */ '../views/About.vue')
},
{
path: '/details/:id',
name: 'Details',
component: () =>
import ( /* webpackChunkName: "about" */ '../views/Details.vue')
},
{
path: '*',
name: 'NotFound',
component: () =>
import ( /* webpackChunkName: "about" */ '../views/NotFound.vue')
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
main.js
import Vue from 'vue'
import App from './App.vue'
import './registerServiceWorker'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
点击跳转:
以前如何实现导航高亮效果?
有没有更简单的方式呢?
可用组件router-link来替代a标签:
vue-router提供了一个全局组件 router-link
router-link实质上最终会渲染成a链接 to属性等价于提供 href属性(to无需#)
router-link提供了声明式导航高亮的功能(自带类名)
main.js,
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
router/index.js,
import Vue from 'vue'
import VueRouter from 'vue-router'
import Find from '../views/Find.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/find',
name: 'Find',
component: Find
},
{
path: '/my',
name: 'My',
component: () => import('../views/My.vue')
},
{
path: '/part',
name: 'Part',
component: () => import('../views/Part.vue')
}
]
const router = new VueRouter({
routes
})
export default router
App.vue,
views/Find.vue,
find
views/My.vue,
my
views/Part.vue,
part
在跳转路由时, 可以给路由对应的组件内传值
方式一:
在router-link上的to属性传值, 语法格式如下:
/path?参数名=值
对应页面组件接收传递过来的值:
$route.query.参数名
方式二:
在router-link上的to属性传值, 语法格式如下:
/path/值 – 需要路由对象提前配置 path: “/path/参数名”
对应页面组件接收传递过来的值:
$route.params.参数名
main.js,
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
router/index.js,
import Vue from 'vue'
import VueRouter from 'vue-router'
import Find from '../views/Find.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/find',
name: 'Find',
component: Find
},
{
path: '/my',
name: 'My',
component: () => import('../views/My.vue')
},
{
path: '/part',
name: 'Part',
component: () => import('../views/Part.vue')
},
{ // 传值方式二:动态路由
path: '/part/:username',
name: 'Part',
component: () => import('../views/Part.vue'),
props: true
}
]
const router = new VueRouter({
routes
})
export default router
App.vue,
views/Part.vue,
part
人名:{{$route.query.name}}
人名2:{{$route.params.username}}
props-人名2:{username}}
在router/index.js 里引入 NotFound.vue
// 404 一定要在组最后
{
path: '*',
name: 'NotFound',
component: () => import('../views/NotFound.vue')
}
路由的路径看起来不自然, 能否切成真正路径形式?
如何切换路由模式呢?
在src/router/index.js文件中做如下修改:
当使用 history 模式时,URL 就像正常的 url,例如 http://yoursite.com/user/id
不过这种模式要玩好,还需后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确配置,当用户在浏览器直接访问 http://oursite.com/user/id
就会返回 404
所以,要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html
页面,这个页面就是你 app 依赖的页面
推荐在开发时使用 hash 模式
除了使用
标签来定义导航链接,还可以借助 router 的实例方法,通过编写代码来实现 :--用JS代码来进行跳转
语法: path或者name任选一个
注意点:虽然用 name 来进行跳转,但是 url 的 hash 值还是切换 path 路径值
index.js,
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '/', // 默认 hash 值路径
redirect: '/find', // 重定向到 /find
// 浏览器 url 中 # 后的路径被修改成 /find-重新匹配规则
component: () => import('../views/Find.vue')
},
{
path: '/find',
name: 'Find',
component: () => import('../views/Find.vue')
},
{
path: '/my',
name: 'My',
component: () => import('../views/My.vue')
},
{
path: '/part',
name: 'Part',
component: () => import('../views/Part.vue')
},
// { // 传值方式二:动态路由
// path: '/part/:username',
// name: 'Part',
// component: () => import('../views/Part.vue')
// },
// 404 一定要在组最后
{
path: '*',
name: 'NotFound',
component: () => import('../views/NotFound.vue')
}
]
const router = new VueRouter({
routes
})
export default router
App.vue,
其余 .vue 省略
应用场景 :
方便修改:name 路由名(在页面上不可见因此可以随便定义 name 对应的值)
path可以在 url 的 hash 值看到
JS跳转路由, 传参
语法: query或者params任选一个
注意: 使用path会忽略params,因此 path 和 params 不能一起使用
推荐:使用 name + query 方式传参
index.js,
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
// 解决报错:Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation to current location: xxx
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push (location) {
return originalPush.call(this, location).catch(err => err)
}
const routes = [
{
path: '/', // 默认 hash 值路径
redirect: '/find', // 重定向到 /find
// 浏览器 url 中 # 后的路径被修改成 /find-重新匹配规则
component: () => import('../views/Find.vue')
},
{
path: '/find',
name: 'Find',
component: () => import('../views/Find.vue')
},
{
path: '/my',
name: 'My',
component: () => import('../views/My.vue')
},
{
path: '/part',
name: 'Part',
component: () => import('../views/Part.vue')
},
// { // 传值方式二:动态路由
// path: '/part/:username',
// name: 'Part',
// component: () => import('../views/Part.vue')
// },
// 404 一定要在组最后
{
path: '*',
name: 'NotFound',
component: () => import('../views/NotFound.vue')
}
]
const router = new VueRouter({
routes
})
export default router
App.vue,
Part.vue
part
人名:{{$route.query.name}}
人名2:{{$route.params.username}}
注意点:如果不断地点击一个导航,例如不断地点击 “朋友--小智”,这个导航,可能会报如下错:
Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation to current location: xxx
解决措施:在 router/index.js 里加上:
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push (location) {
return originalPush.call(this, location).catch(err => err)
}
即可解决
main.js,
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
render: h => h(App)
}).$mount('#app')
router/index.js,
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
// 解决报错:Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation to current location: xxx
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push (location) {
return originalPush.call(this, location).catch(err => err)
}
const routes = [
{
path: '/', // 默认 hash 值路径
redirect: '/find', // 重定向到 /find
// 浏览器 url 中 # 后的路径被修改成 /find-重新匹配规则
component: () => import('../views/Find.vue')
},
{
path: '/find',
name: 'Find',
component: () => import('../views/Find.vue'),
children: [
{
path: 'ranking',
component: () => import('../views/Ranking.vue')
},
{
path: 'recommend',
component: () => import('../views/Recommend.vue')
},
{
path: 'songlist',
component: () => import('../views/SongList.vue')
}
]
},
......
]
const router = new VueRouter({
routes
})
export default router
Find.vue,
App.vue,
...
在一个路由上同时 (同级) 展示多个视图(页面),而不是嵌套展示
在App.vue中新增如下内容:
在src/views目录中新增 AD.vue文件:
广告页面
在src/router/index.js文件中做如下修改:
新增,import AD from "../views/AD.vue"
在 path:'/about' 中做如下修改,
path: '/about',
name: 'About',
// component: () =>
// import ( /* webpackChunkName: "about" */ '../views/About.vue'),
components: {
default: () =>
import ( /* webpackChunkName: "about" */ '../views/About.vue'),
ad: AD
},
主要用来通过跳转或取消的方式守卫导航
有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的
参数或查询的改变并不会触发进入/离开的导航守卫。你可以通过观察 $route 对象来应对这些变化,或使用 beforeRouteUpdate
的组件内守卫
应用场景:需要对路由权限判断时
语法: router.beforeEach((to, from, next)=>{//路由跳转"之前"先执行这里, 决定是否跳转})
所有的路由跳转都会触发该函数
推荐写法:
// GOOD
router.beforeEach((to, from, next) => {
if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
else next()
})
每个守卫方法接收三个参数:
to:
要跳转到的路由 (路由对象信息) 目标
from:
从哪里跳转的路由 (路由对象信息) 来源
next:
函数体 - next()才会让路由正常的跳转切换, next(false)在原地停留, next("强制修改到另一个路由路径上");注意: 如果不调用next, 页面留在原地
next()
: 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
next(false)
: 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from
路由对应的地址。
next('/')
或者 next({ path: '/' })
: 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next
传递任意位置对象,且允许设置诸如 replace: true
、name: 'home'
之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项。
next(error)
: (2.4.0+) 如果传入 next
的参数是一个 Error
实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。
在src/router/index.js中添加如下内容:
router.beforeEach((to, from, next) => {
console.log(from);
console.log(to);
next(); //必须调用
})
beforeResolve
)和 router.beforeEach
类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用
里面的参数都和 全局前置守卫 一样
和守卫不同的是,这些钩子不会接受 next
函数也不会改变导航本身:
在src/router/index.js中添加如下内容:
router.afterEach((to, from) => {
console.log(from);
console.log(to);
})
beforeEnter)
只有某一个路由在发生跳转时才会触发
在src/router/index.js中 about 里做如下修改:
在路由组件内直接定义以下路由导航守卫:
beforeRouteEnter
beforeRouteUpdate
(2.2 新增)beforeRouteLeave
在src/router/index.js中做如下修改:
在App.vue中做如下修改:
src/views/About.vue
This is an about page
id={{$route.params.id}}
进入,
更新,
离开,
beforeRouteLeave
守卫。beforeEach
守卫。beforeRouteUpdate
守卫 (2.2+)。beforeEnter
。beforeRouteEnter
。beforeResolve
守卫 (2.5+)。afterEach
钩子。beforeRouteEnter
守卫中传给 next
的回调函数,创建好的组件实例会作为回调函数的参数传入。meta
字段
在上面的基础之上,在src/views中新建Login.vue:
登录页面
在src/router/index.js里const routes = []字段里增加如下内容:
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about/:id',
name: 'About',
component: () =>
import ('../views/About.vue'),
/**
* 路由独享导航守卫
*/
beforeEnter: (to, from, next) => {
next();
},
meta: {
requiresAuth: true
}
},
{
path: '/login',
name: 'Login',
component: () =>
import ('../views/Login.vue')
},
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
/**
* 全局前置导航守卫
*/
router.beforeEach((to, from, next) => {
// console.log(from);
// console.log(to);
//需要判断用户是否登录
if (to.matched.some(record => record.meta.requiresAuth)) {
//用户已经登录
const token = false;
// if (token) {
// next();
// } else {
// next('/login');
// } 或
token ? next() : next('/login');
} else {
//无需要判断用户是否登录
next();
}
next(); //必须调用
})
export default router
点击“About”时,
router-link-exact-active:
(精确匹配) url中hash值路径, 与href属性值完全相同, 设置此类名
例如:
router-link-active:
(模糊匹配) url中hash值, 包含href属性值这个路径,因此两者都会匹配
例如:
在App.vue的style里面添加如下内容即可:
#nav a.router-link-exact-active {
color: #42b983;
}
在src/router/index.js的const router = new VueRouter()字段里添加如下内容:
const router = new VueRouter({
linkActiveClass: "active",
linkExactActiveClass: "exact-active",
})
在
最后App.vue的style里面的 #nav a.router-link-exact-active 字段,便可直接用 .active替代
App.vue
src/views/Home.vue
src/views/About.vue
This is an about page
id={{$route.params.id}}
src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
Vue.use(VueRouter)
const routes = [{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about/:id',
name: 'About',
component: () =>
import ('../views/About.vue'),
/**
* 路由独享导航守卫
*/
beforeEnter: (to, from, next) => {
next();
},
meta: {
requiresAuth: true
}
},
{
path: '/login',
name: 'Login',
component: () =>
import ('../views/Login.vue')
},
]
const router = new VueRouter({
mode: 'history',
linkActiveClass: "active",
linkExactActiveClass: "exact-active",
base: process.env.BASE_URL,
routes
})
/**
* 全局前置导航守卫
*/
router.beforeEach((to, from, next) => {
// console.log(from);
// console.log(to);
//需要判断用户是否登录
if (to.matched.some(record => record.meta.requiresAuth)) {
//用户已经登录
const token = true;
// if (token) {
// next();
// } else {
// next('/login');
// } 或
token ? next() : next('/login');
} else {
//无需要判断用户是否登录
next();
}
next(); //必须调用
})
/**
* 全局后置导航守卫
*/
// router.afterEach((to, from) => {
// // console.log(from);
// // console.log(to);
// })
export default router