vue-router

Vue Router 是 Vue.js 官⽅的路由管理器。它和 Vue.js 的核⼼深度集成,让构建单页面应用变得易如反掌。

  • 嵌套的路由/视图表
  • 模块化的、基于组件的路由配置
  • 路由参数、查询、通配符
  • 基于 Vue.js 过渡系统的视图过渡效果
  • 细粒度的导航控制
  • 带有自动激活的 CSS class 的链接
  • HTML5 历史模式或 hash 模式,在 IE9 中自动降级
  • 自定义的滚动条行为

安装: vue add router

核⼼步骤:

步骤⼀:使⽤vue-router插件,router.js

import Router from 'vue-router'
Vue.use(Router)

步骤二: 声明一个路由表(是一个映射表,path和组件是一个映射关系)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
  }
]

步骤三:创建Router实例,router.js(然后导出)

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

步骤四: 在根组件上添加该实例,main.js

import router from './router'
new Vue({
    router,
}).$mount("#app")

步骤五: 添加路由视图,App.vue

<router-view></router-view>

导航链接

<router-link to="/">Home</router-link> 
<router-link to="/about">About</router-link>

 

以上大概是vue-router的基本使用,下面进入简版源码实现部分

vue-router源码实现

需求分析

  • spa 页面不能刷新
    • hash方式 例如: #/home
    • 或者History api 例如: /about
  • 根据url显示对应的内容
    • router-view
    • 数据响应式: current变量持有url地址,一旦变化,动态重新执行render

任务

  • 实现一个插件(创建VueRouter类和install⽅法)
    • 实现VueRouter类
      • 处理路由选项
      • 监控url的变化
      • 相应变化
    • 实现一个install 方法
      • $router的注册
      • 两个全局组件

创建kvue-router.js

let Vue; // 引⽤构造函数,VueRouter中要使⽤
// 保存选项
class VueRouter {
   constructor(options) {
     this.$options = options;
   }
}
// 插件:实现install⽅法,注册$router
VueRouter.install = function(_Vue) {
  // 引⽤构造函数,VueRouter中要使⽤
  Vue = _Vue;
  // 任务1:挂载$router
  Vue.mixin({
	beforeCreate() {
	  // 只有根组件拥有router选项
	  if (this.$options.router) {
	  // vm.$router
	    Vue.prototype.$router = this.$options.router;
	  }
	}
  });
  // 任务2:实现两个全局组件router-link和router-view
  Vue.component('router-link', Link)
  Vue.component('router-view', View)
};
export default VueRouter;

为什么要⽤混⼊⽅式写?主要原因是use代码在前,Router实例创建在后,⽽install逻辑⼜需要⽤ 到该实例,这里是一个比较巧妙的方法(主要是为了延迟执行)

创建router-view和router-link

创建krouter-link.js

 export default {
    // 定义传入的参数
    props: {
      to: {
        type: String,
        require: true
      }
    },
    render(h) {
      // <router-link to="/home"/>
      // <a href="#/home">XXX</a>
      // 通用性更好
      return h('a',{
        attrs: {
          href: '#' + this.to
        }
      }, this.$slots.default)
      // 需要当前环境支持jsx
      // return <a href={'#' + this.to}> {this.$slots.default}</a>
    }
  }

创建krouter-view.js

export default {
  render(h) {
  // 暂时先不渲染任何内容
    return h(null);
  }
}

监控url变化
定义响应式的current属性,监听hashchange事件

class VueRouter {
  constructor(options) {
  // current应该是响应式的
  Vue.util.defineReactive(this, 'current', '/')
  // 定义响应式的属性current
  const initial = window.location.hash.slice(1) || '/'
  Vue.util.defineReactive(this, 'current', initial)
    // 监听hashchange事件
    window.addEventListener('hashchange', this.onHashChange.bind(this))
    window.addEventListener('load', this.onHashChange.bind(this))
  }
  onHashChange() { 
    this.current = window.location.hash.slice(1)
 }
}

动态获取对应组件,krouter-view.js

export default {
  render(h) {
  // 动态获取对应组件
    let component = null;
    this.$router.$options.routes.forEach(route => {
    if (route.path === this.$router.current) {
      component = route.component
    } 
   });
   return h(component);
  }
}

提前处理路由表
提前处理路由表避免每次都循环

class VueRouter {
  constructor(options) {
	// 缓存path和route映射关系
	this.routeMap = {}
	this.$options.routes.forEach(route => {
	  this.routeMap[route.path] = route
	});
  }
}

使⽤,krouter-view.js

export default {
  render(h) {
    const {routeMap, current} = this.$router
    const component = routeMap[current] ? routeMap[current].component : null;
    return h(component);
  }
}

下面附上完整代码

// 实现一个插件
// 1. 返回一个函数
// 2. 或者返回一个对象,它有一个install方法


let _Vue = null
/**
 * @class VueRouter
 * @param options 选项
 */

class VueRouter {
  constructor(options) {
    // options 配置选项: router - 路由表
    this.$options = options

    // 缓存path 和route的映射关系
    this.routeMap = {}

     // 找到当前url对于的组件
    this.$options.routes.forEach(route => {
      this.routeMap[route.path] = route
    })

    // 需要定义一个响应式的current属性
    const initial = window.location.hash.slice(1) || '/'
    // defineReactive 给一个对象定义响应式数据
    _Vue.util.defineReactive(this, 'current', initial)

    // 监控url的变化
    window.addEventListener('hashchange', this.onHashChange.bind(this))

  }
  onHashChange () {
    this.current = window.location.hash.slice(1)
    console.log(this.current)
  }
}

VueRouter.install = function(Vue) {
  // 引用Vue构造函数,在上面的VueRouter中使用
  _Vue= Vue
  // 1. 挂载$router
  // 利用混入,延迟执行
  Vue.mixin({
    // 此处的this 指的是vue根实例
    beforeCreate() {
      if(this.$options.router) {
        Vue.prototype.$router = this.$options.router
      }
    }
  })

  // 2. 定义两个全局组件router-link, router-view
  Vue.component('router-link', {
    // 定义传入的参数
    props: {
      to: {
        type: String,
        require: true
      }
    },
    render(h) {
      // <router-link to="/home"/>
      // <a href="#/home">XXX</a>
      // 通用性更好
      return h('a',{
        attrs: {
          href: '#' + this.to
        }
      }, this.$slots.default)
      // 需要当前环境支持jsx
      // return <a href={'#' + this.to}> {this.$slots.default}</a>
    }
  })
  Vue.component('router-view', {
    render(h) {
      // 找到当前的url对应的组件
      const { routeMap, current } = this.$router
      const component = routeMap[current] ? routeMap[current].component : null
      return h(component)
    }
  })
}

export default VueRouter