配置 Vue 实例( 路由 )

文章目录

  • 一、基本概念
    • 1.1 route(单条路由)
    • 1.2 routes(所有路由)
    • 1.3 router(路由控制器)
    • 1.4 配置 router(注入 Vue 实例)
    • 1.5 使用 router
    • 1.6 渲染组件
  • 二、基本使用
    • 2.1 配置示例
    • 2.2 渲染示例
    • 2.3 跳转示例
      • 2.3.1 声明式跳转
      • 2.3.2 编程式跳转
      • 2.3.3 接收参数和数据
      • 2.3.4 动态替换路由(地址栏)参数
  • 三、导航守卫
    • 3.1 导航守卫是什么
    • 3.2 全局路由钩子
      • 3.2.1 beforeEach(to,from, next)
      • 3.2.2 beforeResolve(to,from, next)
      • 3.2.3 afterEach(to,from)
    • 3.3 路由独享钩子
      • 3.3.1 beforeEnter(to,from, next)
    • 3.4 组件内路由钩子
      • 3.4.1 beforeRouteEnter(to,from, next)
      • 3.4.2 beforeRouteUpdate(to,from, next)
      • 3.4.3 beforeRouteLeave(to,from, next)
    • 3.5 导航守卫回调参数
  • 四、插件基本实现

客户端中的路由,实际上就是 DOM 元素的显示和隐藏。当页面中显示 home 内容的时候, about 中的内容全部隐藏,反之也是一样。客户端路由有两种实现方式:基于 hash 和基于 html5 history api

一、基本概念

1.1 route(单条路由)

定义每一条路由

const indexRoute = { path: '/index', component: Index }
const userRoute = { path: '/user', component: User }

1.2 routes(所有路由)

将定义好的每一条路由整合成一个数组

const routes = [
  indexRoute,
  userRoute
]

1.3 router(路由控制器)

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

1.4 配置 router(注入 Vue 实例)

Vue 实例中配置 router,就可以使用了路由了

import router from "./router.js"

const app = new Vue({
  router
}).$mount('#app')

1.5 使用 router

当用户点击 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>

1.6 渲染组件

最后把组件渲染到 router-view 标签所在的地方

<template>
  <div id="app">
    ......
    <router-view></router-view>
		......
  </div>
</template>

二、基本使用

2.1 配置示例

// 引入需要的功能模块
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')
                    }
                }
            ]
        }
    ]
})

2.2 渲染示例


<router-view>router-view> 

<router-view name="header">router-view>

【注】路由配置中 children 中的子组件要渲染的话,也要在父组件中加入

2.3 跳转示例

2.3.1 声明式跳转

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:'...'}'

2.3.2 编程式跳转

不需要像声明式跳转一样将标签改为 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()

2.3.3 接收参数和数据

  • 模板: {{ $route.params || query || path }}
  • 组件: this.$route.params || query

2.3.4 动态替换路由(地址栏)参数

import merge from 'webpack-merge'// 作用的参数如果存在的话就改变,没有的话就新增   
this.$router.push({
    query:merge(this.$route.query,{'num':'630'})
}) 
// 替换所有参数:
 this.$router.push({
    query:merge({},{'num':'630'})
})

三、导航守卫

3.1 导航守卫是什么

正如其名,vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。

其实,导航守卫就是路由跳转过程中的一些钩子函数,再直白点路由跳转是一个大的过程,这个大的过程分为跳转前中后等等细小的过程,在每一个过程中都有一函数,这个函数能让你操作一些其他的事儿的时机,这就是导航守卫。

3.2 全局路由钩子

路由实例上直接操作的钩子函数,他的特点是所有路由配置的组件都会触发,直白点就是触发路由就会触发这些钩子函数,如下的写法。钩子函数按执行顺序包括 beforeEachbeforeResolve 以及 afterEach 三个(以下的钩子函数都是按执行顺序讲解的)

const router = new VueRouter({ ... })

router.beforeEach((to, from, next) => {
  // ...
})

3.2.1 beforeEach(to,from, next)

在路由跳转前触发,这个钩子作用主要是用于登录验证,也就是路由还没跳转提前告知,以免跳转了再通知就为时已晚

3.2.2 beforeResolve(to,from, next)

这个钩子和 beforeEach 类似,也是路由跳转前触发,和 beforeEach 区别官方解释为:

区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。

即在 beforeEach 和组件内 beforeRouteEnter 之后,afterEach 之前调用。

3.2.3 afterEach(to,from)

beforeEach 相反,他是在路由跳转完成后触发,他发生在 beforeEachbeforeResolve 之后,beforeRouteEnter(组件内守卫,后讲)之前

3.3 路由独享钩子

单个路由配置的时候也可以设置的钩子函数,其位置就是下面示例中的位置,也就是像 Foo 这样的组件都存在这样的钩子函数。目前他只有一个钩子函数 beforeEnter

const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: (to, from, next) => {
        // ...
      }
    }
  ]
})

3.3.1 beforeEnter(to,from, next)

beforeEach 完全相同,如果都设置则在 beforeEach 之后紧随执行

3.4 组件内路由钩子

在组件内执行的钩子函数,类似于组件内的生命周期,相当于为配置路由的组件添加的生命周期钩子函数。钩子函数按执行顺序包括 beforeRouteEnterbeforeRouteUpdate (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>

3.4.1 beforeRouteEnter(to,from, next)

路由进入之前调用,该钩子在全局守卫 beforeEach 和独享守卫 beforeEnter 之后,全局 beforeResolve 和全局 afterEach 之前调用,要注意的是该守卫内访问不到组件的实例,也就是 thisundefined ,也就是他在 beforeCreate 生命周期前触发。在这个钩子函数中,可以通过传一个回调给 next 来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数,可以在这个守卫中请求服务端获取数据,当成功获取并能进入路由时,调用 next 并在回调中通过 vm 访问组件实例进行赋值等操作,(next 中函数的调用在 mounted 之后:为了确保能对组件实例的完整访问)。

beforeRouteEnter (to, from, next) {
  // 这里还无法访问到组件实例,this === undefined
  next( vm => {
    // 通过 `vm` 访问组件实例
  })
}

3.4.2 beforeRouteUpdate(to,from, next)

当前路由改变且该组件被复用时调用,可以通过this访问实例。

  • 当前路由改变:query 变更
  • 该组件被复用:对于一个带有动态参数的路径 /foo/:id ,在 /foo/1/foo/2 之间跳转的时候,组件实例会被复用,该守卫会被调用

3.4.3 beforeRouteLeave(to,from, next)

导航离开该组件的对应路由时调用,可以访问组件实例 this

3.5 导航守卫回调参数

to:目标路由对象
from:即将要离开的路由对象
next:他是最重要的一个参数,起到了承上启下的作用。以下注意点务必牢记:

  • 但凡涉及到有 next 参数的钩子,必须调用 next() 才能继续往下执行下一个钩子,否则路由跳转等会停止。
  • 如果要中断当前的导航要调用 next(false) 。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。(主要用于登录验证不通过的处理)
  • 当然 next 可以这样使用,next(’/’) 或者 next({ path: ‘/’ }): 跳转到一个不同的地址。意思是当前的导航被中断,然后进行一个新的导航。可传递的参数与 router.push 中选项一致。
  • beforeRouteEnter 钩子中 next((vm) => {}) 内接收的回调函数参数为当前组件的实例 vm,这个回调函数在生命周期 mounted 之后调用,也就是,他是所有导航守卫和生命周期函数最后执行的那个钩子。
  • next(error): (v2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。

四、插件基本实现

/**
 * 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;

你可能感兴趣的:(Vue.js,vue.js,javascript,路由,插件,导航守卫)