定义每一条路由
const indexRoute = { path: '/index', component: Index }
const userRoute = { path: '/user', component: User }
将定义好的每一条路由整合成一个数组
const routes = [
indexRoute,
userRoute
]
import Vue from "vue";
import VueRouter from "vue-router";
Vue.use(VueRouter);
const routes = [
{ path: '/index', component: Index },
{ path: '/user', component: User }
]
const router = new VueRouter({
routes
})
export default router
在 Vue 实例中配置 router,就可以使用了路由了
import router from "./router.js"
const app = new Vue({
router
}).$mount('#app')
当用户点击 router-link 标签后,会用 to 属性去匹配 routes 中配置的路径 path,从而找到了匹配的组件
<template>
<div id="app">
......
<router-link to="/index">Index</router-link>
<router-link to="/user">User</router-link>
......
</div>
</template>
最后把组件渲染到 router-view 标签所在的地方
<template>
<div id="app">
......
<router-view></router-view>
......
</div>
</template>
// 引入需要的功能模块
import Router from 'vue-router'
// 引入需要渲染的组件
import Header from '@/components/Header'
// 安装 || 注册插件到全局
Vue.use(Router)
// 创建路由(传递配置)
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
// 配置路由(建立组件和请求的对应关系)
routes: [
// 重定向
{path: '/',redirect: '/home'},
{
path: '/home', // 路径
// 指向的组件变量名可以唯一标识一个路由,当path嵌套过深的时候,可以解决path过长的问题
name: 'home',
components: {
header: Header, // 注册已经引入的组件
default: () => import('@/views/home/index.vue') // 路由懒加载
},
// 子路由配置
children: [
{path: '',redirect: 'concern'},
{
path: 'concern',
name: 'concern',
components:{
default: () => import('@/components/home/Concern.vue')
}
},
/**
* 动态路由配置{path:'xx/:动态路由变量',component:xx}
* 动态路由在来回切换时,由于都是指向同一组件,vue 不会销毁再创建,而是复用这个组件,所以不触发各别组件的生命周期
* 这时如果想要在组件来回切换的时候做点事情,那么只能在组件内部利用 watch 来监听$route 的变化
*/
{
path: '/concern-detail/:id',
name: 'concern-detail',
components: {
default: () => import('@/components/ConcernDetail.vue')
}
}
]
}
]
})
<router-view>router-view>
<router-view name="header">router-view>
【注】路由配置中 children 中的子组件要渲染的话,也要在父组件中加入
router-link to='xx/路由名?a=1b=2'
/**
* name相当于起了一个名字,可以唯一标识一个路由,当path嵌套过深的时候,可以解决path过长的问题。
* 当使用对象作为路由的时候,to前面要加一个冒号,表示绑定
*/
router-link :to='{path:'',name:'xx',params:{id:1},query:{a:2,b:3},redirect:'...'}'
不需要像声明式跳转一样将标签改为 router-link,再编译回原来的标签,编程式跳转直接使用原生 HTML 的标签即可,只需在上面绑定一个事件,在事件中使用下方的语法即可完成跳转
// 添加一个路由 (记录到历史记录)
this.$router.push(xx/路由名?a=1b=2)
this.$router.push({name:'xx',params:{id:1},query:{a:2,b:3}})
// 替换一个路由 (不记录到历史记录)
this.$router.replace({name:'...'})
// 回退/前进
this.$router.go(-1|1)|goBack()
import merge from 'webpack-merge';
// 作用的参数如果存在的话就改变,没有的话就新增
this.$router.push({
query:merge(this.$route.query,{'num':'630'})
})
// 替换所有参数:
this.$router.push({
query:merge({},{'num':'630'})
})
正如其名,vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。
其实,导航守卫就是路由跳转过程中的一些钩子函数,再直白点路由跳转是一个大的过程,这个大的过程分为跳转前中后等等细小的过程,在每一个过程中都有一函数,这个函数能让你操作一些其他的事儿的时机,这就是导航守卫。
路由实例上直接操作的钩子函数,他的特点是所有路由配置的组件都会触发,直白点就是触发路由就会触发这些钩子函数,如下的写法。钩子函数按执行顺序包括 beforeEach、beforeResolve 以及 afterEach 三个(以下的钩子函数都是按执行顺序讲解的)
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
// ...
})
在路由跳转前触发,这个钩子作用主要是用于登录验证,也就是路由还没跳转提前告知,以免跳转了再通知就为时已晚
这个钩子和 beforeEach 类似,也是路由跳转前触发,和 beforeEach 区别官方解释为:
区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
即在 beforeEach 和组件内 beforeRouteEnter 之后,afterEach 之前调用。
和 beforeEach 相反,他是在路由跳转完成后触发,他发生在 beforeEach 和 beforeResolve 之后,beforeRouteEnter(组件内守卫,后讲)之前
单个路由配置的时候也可以设置的钩子函数,其位置就是下面示例中的位置,也就是像 Foo 这样的组件都存在这样的钩子函数。目前他只有一个钩子函数 beforeEnter
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
和 beforeEach 完全相同,如果都设置则在 beforeEach 之后紧随执行
在组件内执行的钩子函数,类似于组件内的生命周期,相当于为配置路由的组件添加的生命周期钩子函数。钩子函数按执行顺序包括 beforeRouteEnter、beforeRouteUpdate (2.2+) 、beforeRouteLeave 三个,执行位置如下:
<template>
...
</template>
export default{
data(){
//...
},
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
<style>
...
</style>
路由进入之前调用,该钩子在全局守卫 beforeEach 和独享守卫 beforeEnter 之后,全局 beforeResolve 和全局 afterEach 之前调用,要注意的是该守卫内访问不到组件的实例,也就是 this 为 undefined ,也就是他在 beforeCreate 生命周期前触发。在这个钩子函数中,可以通过传一个回调给 next 来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数,可以在这个守卫中请求服务端获取数据,当成功获取并能进入路由时,调用 next 并在回调中通过 vm 访问组件实例进行赋值等操作,(next 中函数的调用在 mounted 之后:为了确保能对组件实例的完整访问)。
beforeRouteEnter (to, from, next) {
// 这里还无法访问到组件实例,this === undefined
next( vm => {
// 通过 `vm` 访问组件实例
})
}
当前路由改变且该组件被复用时调用,可以通过this访问实例。
导航离开该组件的对应路由时调用,可以访问组件实例 this
to:目标路由对象
from:即将要离开的路由对象
next:他是最重要的一个参数,起到了承上启下的作用。以下注意点务必牢记:
/**
* VueRouter 插件
* 最基本的路由功能
* 要求必须有一个 install,将来会被 Vue.use 调用
*/
let Vue; // 保存 Vue 构造函数,插件中要使用,不导入还能用
class VueRouter {
constructor(options) {
this.$options = options;
// 需要创建响应式的 matched 属性
// 利用 Vue 提供的 defineReactive 做响应化
// 这样将来 matched 变化的时候,依赖的组件会重新 render
this.current = window.location.hash.slice(1) || "/";
Vue.util.defineReactive(this, "matched", []);
this.match();
// 监听hash变化
window.addEventListener("hashchange", this.onHashChange.bind(this));
window.addEventListener("load", this.onHashChange.bind(this));
}
onHashChange() {
this.current = window.location.hash.slice(1);
this.matched = [];
this.match();
}
// 『Task 3』兼容嵌套路由
// 递归遍历路由表,获得匹配关系数组
match(routes) {
routes = routes || this.$options.routes;
// 递归遍历
for (const route of routes) {
if (route.path === "/" && this.current === "/") {
this.matched.push(route);
return;
}
// /about/info
if (route.path !== "/" && this.current.indexOf(route.path) !== -1) {
this.matched.push(route);
if (route.children) {
this.match(route.children)
}
return;
}
}
}
}
// 参数是 Vue 构造函数
VueRouter.install = function(_Vue) {
/**
* 保存构造函数
* 目的:因为该插件是一个独立的包,在打包的时候不希望把 Vue 也打包进去
*/
Vue = _Vue;
/**
* 全局混入
* 目的:延迟里面的逻辑到 router 已经创建完毕,并且附加到选项上时才执行
*/
Vue.mixin({
// 组件实例化的时候才执行
beforeCreate() {
// 根实例才有该选项,this 指的是组件实例
if (this.$options.router) {
// 『Task 1』挂载 $router 属性
Vue.prototype.$router = this.$options.router;
}
}
});
// 『Task 2』注册实现两个组件:router-link router-view
Vue.component("router-link", {
props: {
to: {
type: String,
required: true
}
},
render(h) {
// 也可以使用 JSX 写法,但是兼容性差(需要配置相关的loader),不推荐使用
// return { this.$slots.default };
return h("a", { attrs: { href: `#${this.to}` } }, this.$slots.default);
}
});
Vue.component("router-view", {
render(h) {
// 标记当前的 router-view 深度
this.$vnode.data.routerView = true;
let depth = 0;
let parent = this.$parent;
while (parent) {
const vnodeData = parent.$vnode && parent.$vnode.data;
if (vnodeData) {
if (vnodeData.routerView) {
// 说明当前 parent 是一个 router-view
depth++;
}
}
parent = parent.$parent;
}
// 获取当前路由对应的组件
let component = null;
const route = this.$router.matched[depth];
if (route) {
component = route.component;
}
return h(component);
}
});
};
export default VueRouter;