Vue Router
Vue Router是 Vue.js 官方的路由管理器。
一、安装
vue add router
二、基础
(一) 起步
路由规划、配置,router/index.js
商品列表(home) - 商品管理(about)
- 路由出口、导航,App.vue
<nav>
<router-link to="/">首页</router-link>
<router-link to="/about">管理</router-link>
</nav>
<router-view></router-view>
- 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: '/admin',
name: 'Admin',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
//懒加载
//把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件
//结合 Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载。
//https://router.vuejs.org/zh/guide/advanced/lazy-loading.html
component: () => import('../views/Admin.vue')
}
]
const router = new VueRouter({
routes
})
export default router
(二) 动态路由匹配
我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,我们有一个 User
组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染。那么,我们可以在 vue-router
的路由路径中使用“动态路径参数”(dynamic segment) 来达到这个效果:
{
path: '/user/:id', component: User }
范例:查看课程详情
- views/Detail.vue
<div>
<h2>detail page</h2>
<p>{
{$route.params.name}}</p>
</div>
- router/index.js
{
path: '/course/:name', component: () => import('../views/Detail.vue')
}
- LIst.vue
<router-link :to="`/course/${c.name}`">
{
{ c.name }} - {
{ c.price | currency('¥') }}
</router-link>
2.2.1 捕获所有路由或 404 Not found 路由
{
// 会匹配所有路径
path: '*', component: () => import('../views/404.vue')
}
(三) 嵌套路由
实际生活中的应用界面,通常由多层嵌套的组件组合而成。同样地,URL 中各段动态路径也按某种结构
对应嵌套的各层组件,例如:
/user/foo/profile /user/foo/posts
+------------------+ +-----------------+
| User | | User |
| +--------------+ | | +-------------+ |
| | Profile | | +------------> | | Posts | |
| | | | | | | |
| +--------------+ | | +-------------+ |
+------------------+ +-----------------+
范例:嵌套方式显示商品详情
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: '/admin',
name: 'Admin',
component: () => import( '../views/Admin.vue'),
children:[
{
path:'/detail/:name',
name:'Detail',
component: () => import( '../views/Detail.vue'),
}
]
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
Admin.vue
<template>
<div class="about">
<h1>This is an admin page</h1>
<div v-for="(item, index) in list" :key="index">
<router-link :to="`/detail/${item.id}`">
<div>{
{ item.name }} -- {
{item.price}}</div>
</router-link>
</div>
<router-view></router-view>
</div>
</template>
<script>
export default {
data() {
return {
list: [
{
name: "萝卜", price: 100, id:1 },
{
name: "白菜", price: 50, id:2 },
],
};
},
};
</script>
Detail.vue
<template>
<div>
<h1>detail页面</h1>
{
{$route.params.id}}
</div>
</template>
<script>
export default {
created () {
//detail页面变化的时候,地址是参数在变化,
//这个组件在创建创建的第一次,以后再切换的时候,发现不再发送
//内在的原因是组件的复用,为了提高程序的效率
//发现detail这个组件不需要重新创建,只是复用就可以了,这个组件不会在销毁,重新创建了。
//没有机会让它多次去执行这个 created 的生命周期
//解决方式:写一个对 $route 的监听器
console.log('通过id,发送详情的请求');
},
watch: {
//带选项的监听,第一次进来的时候获取一次数据,以后发生变化的时候依然重新调用,就不用再created中再写一次
$route: {
handler: () => {
//发现 $route 发生变化了,就重新请求数据
console.log("$route change----获取详情");
//第一次请求的时候是没有的,但是在列表点击来回切换的时候可以看到
},
immediate: true //我们程序在 created 的时候执行一次
}
}
};
</script>
(四) 编程导航
路由跳转
router.push(location, onComplete?, onAbort?)
// 字符串
router.push('home')
// 对象
router.push({
path: 'home' })
// 命名的路由
router.push({
name: 'user', params: {
userId: '123' }})
// 带查询参数,变成 /register?plan=private
router.push({
path: 'register', query: {
plan: 'private' }})
(五) 命名路由
通过一个名称来标识一个路由显得更方便一些,特别是在链接一个路由,或者是执行一些跳转的时候。你可以在创建 Router
实例的时候,在 routes
配置中给某个路由设置名称。
const router = new VueRouter({
routes: [
{
path: '/user/:userId',
name: 'user',
component: User
}
]
})
要链接到一个命名路由,可以给 router-link
的 to
属性传一个对象:
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
调用 router.push() 时:
router.push({
name: 'user', params: {
userId: 123 }})
三、进阶
(一) 路由守卫
vue-router
提供的导航守卫主要用来通过跳转或取消的方式守卫导航。有多种机会植入路由导航过程中:全局的, 单个路由独享的, 或者组件级的。
比如说有一些路由,用户没有权限的话,他不应当能看到这个路由所对应的内容,这个时候我们对这个路由进行保护,就显得非常重要了。
1.全局守卫
router --> index.js
// ......
const routes = [
// ......
{
path: "/admin",
name: "Admin",
component: () => import("../views/Admin.vue"),
meta: {
auth: true,//admin需要路由守卫
},
},
{
// 会匹配所有路径
path: '*',
component: () => import("../views/NotFound.vue"),
}
];
// ......
// to:
/*
to 去哪里
from 来自哪里
next 应不应该放行的next函数
*/
router.beforeEach((to, from, next) => {
/*
官网的例子
if (to.name !== "Login" && !isAuthenticated) next({ name: "Login" });
else next();
*/
// 判断路由是否需要守卫
// 通过 meta 数据的方式
if(to.meta.auth){
//判断到底需不需要守卫,
//如果需要守卫,则判断用户是否登录
//全局保存登录状态
if(window.isLogin){
//如果已经登录,放行
next();
}else{
//没有登录,不可以放行,进入login页面
next('/login?redirect=' + to.fullPath );//将来登录以后,重定向,还可以回到 我想去的 页面 。 to.fullPath :路由的完整路径,
}
}else{
//如果不需要登录就可以看到的页面
next();//不需要校验验证,可以直接访问。
}
});
// ......
/*解决F12报错问题
//见下图
const originalPush = VueRouter.prototype.push;
VueRouter.prototype.push = function push(location) {
return originalPush.call(this, location).catch((err) => err);
};
*/
Login.vue
<template>
<div>
<button @click="login" v-if="!isLogin">登录</button>
<button @click="logout" v-else>注销</button>
</div>
</template>
<script>
export default {
computed: {
isLogin() {
return window.isLogin
}
},
methods: {
login() {
window.isLogin = true;
this.$router.push(this.$route.query.redirect);
// console.log()
},
logout(){
window.isLogin = false;
this.$router.push('/');
}
},
}
</script>
2.路由独享的守卫
你可以在路由配置上直接定义 beforeEnter
守卫:
{
path: "/admin",
name: "Admin",
component: () => import("../views/Admin.vue"),
beforeEnter: (to, from, next) => {
if(window.isLogin){
next();
}else{
next('/login?redirect=' + to.fullPath );
}
}
},
3.组件内的守卫
Admin.vue
<template>
......
</template>
<script>
export default {
beforeRouteEnter(to, from, next) {
if (window.isLogin) {
next();
} else {
next("/login?redirect=" + to.fullPath);
}
},
};
</script>
(二) 数据获取
有时候,进入某个路由后,需要从服务器获取数据。例如,在渲染用户信息时,你需要从服务器获取用户的数据。我们可以通过两种方式来实现:
- 导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示“加载中”之类的指示。
- 导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航。
1.导航完成后获取数据
当你使用这种方式时,我们会马上导航和渲染组件,然后在组件的 created
钩子中获取数据。这让我们有机会在数据获取期间展示一个 loading
状态,还可以在不同视图间展示不同的 loading 状态。
假设我们有一个 Post
组件,需要基于 $route.params.id
获取文章数据:
<template>
<div class="post">
<div v-if="loading" class="loading">
Loading...
</div>
<div v-if="error" class="error">
{
{ error }}
</div>
<div v-if="post" class="content">
<h2>{
{ post.title }}</h2>
<p>{
{ post.body }}</p>
</div>
</div>
</template>
export default {
data () {
return {
loading: false,
post: null,
error: null
}
},
created () {
// 组件创建完后获取数据,
// 此时 data 已经被 observed 了
this.fetchData()
},
watch: {
// 如果路由有变化,会再次执行该方法
'$route': 'fetchData'
},
methods: {
fetchData () {
this.error = this.post = null
this.loading = true
// replace getPost with your data fetching util / API wrapper
getPost(this.$route.params.id, (err, post) => {
this.loading = false
if (err) {
this.error = err.toString()
} else {
this.post = post
}
})
}
}
}
2.在导航完成前获取数据
通过这种方式,我们在导航转入新的路由前获取数据。我们可以在接下来的组件的 beforeRouteEnter
守卫中获取数据,当数据获取成功后只调用 next
方法。
export default {
data () {
return {
post: null,
error: null
}
},
beforeRouteEnter (to, from, next) {
getPost(to.params.id, (err, post) => {
next(vm => vm.setData(err, post))
})
},
// 路由改变前,组件就已经渲染完了
// 逻辑稍稍不同
beforeRouteUpdate (to, from, next) {
this.post = null
getPost(to.params.id, (err, post) => {
this.setData(err, post)
next()
})
},
methods: {
setData (err, post) {
if (err) {
this.error = err.toString()
} else {
this.post = post
}
}
}
}
(三) 动态路由
通过 router.addRoutes(routes)
方式动态添加路由
上面的路由守卫中,这些路由是提前已经配置好了,但是用户能不能进去,能不能看会做一个拦截判断。
如果这个人压根就没有权限看这个路由,能不能说根据当前用户的登录以后的角色信息,然后觉得他能看什么,我就在路由表加什么。
router --> index.js
路由守卫修改为:要求用户必须登录,否则只能去登录页
......
const routes = [
{
path: "/",
name: "Home",
component: Home,
},
{
path: "/login",
name: "Login",
component: () => import("../views/Login.vue"),
},
// {
// path: "/admin",
// name: "Admin",
// component: () => import("../views/Admin.vue"),
// children: [
// {
// path: "/admin/detail/:name",
// name: "Detail",
// component: () => import("../views/Detail.vue"),
// },
// ],
// // meta: {
// // auth: true, //admin需要路由守卫
// // },
// },
{
// 会匹配所有路径
path: "*",
component: () => import("../views/NotFound.vue"),
},
];
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes,
});
// to:
/*
to 去哪里
from 来自哪里
next 应不应该放行的next函数.
强制用户去登录
*/
router.beforeEach((to, from, next) => {
//判断逻辑
//1.是否登录
if (window.isLogin) {
//如果已经登录,进一步判断
if (to.path === "/login") {
//如果去登录页,去首页
next("/");
} else {
//如果去的不是登录页,则放行
next();
}
} else {
//没有登录
if (to.path === "/login") {
//如果去登录页,就放行
next();
} else {
//如果去的不是登录页,必须去登录页面
next("/login?redirect=" + to.fullPath);
}
}
});
......
Login.vue
Login.vue用户登录成功后动态添加/about
<template>
<div>
<button @click="login" v-if="!isLogin">登录</button>
<button @click="logout" v-else>注销</button>
</div>
</template>
<script>
export default {
computed: {
isLogin() {
return window.isLogin;
},
},
methods: {
login() {
window.isLogin = true;
//动态添加路由
this.$router.addRoutes([
{
path: "/admin",
name: "Admin",
component: () => import("../views/Admin.vue"),
children: [
{
path: "/admin/detail/:name",
name: "Detail",
component: () => import("../views/Detail.vue"),
},
],
},
]);
//去他想去的页面
this.$router.push(this.$route.query.redirect);
},
logout() {
window.isLogin = false;
this.$router.push("/");
},
},
};
</script>
(四) 路由组件缓存
如果确定数据加载完之后,不会发生明确的变化。或者某种固定的条件下才会变化。其实没有必要让它频繁的去加载,浪费资源。
主要用于保留组件状态或避免重新渲染。
利用 keepalive做组件缓存,保留组件状态,提高执行效率。
app.vue
<keep-alive include="admin">
<router-view/>
</keep-alive>
include="admin" 这例子中,要活着的组件只有admin
include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
max - 数字。最多可以缓存多少组件实例。
使用include 或exclude 时,要给组件设置name
当组件在
<keep-alive>
内被切换,它的 activated 和 deactivated 这两个生命周期钩子函数将会被对应执行。
Admin.vue
<template>
......
</template>
<script>
export default {
name:'admin',
......
activated(){
console.log("activated");
},
deactivated(){
console.log("deactivated");
}
};
</script>
(五) 路由懒加载
路由懒加载能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。
() => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
() => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
() => import(/* webpackChunkName: "group-foo" */ './Baz.vue')