回顾核心代码,VueRouter 是 Vue 的一个插件,Vue.use()用来注册一个组件,它接收一个函数或者对象,传入函数会直接调用函数,传入对象会调用对象的 install方法。所以实现 VueRouter 要定义一个静态的install方法。之后用 new 的方法创建了一个 VueRouter 实例,那么应该 VueRouter 应该是一个类或者构造函数。我们用类来实现,这个类接收一个对象作为构造参数。

  1. //router/index.js
  2. Vue.use(VueRouter)
  3. const router = new VueRouter({
  4. routes: [
  5. {
  6. path: '/',
  7. name: 'Home',
  8. component: Home
  9. },
  10. ]
  11. })
  12. // main.js
  13. new Vue({
  14. // 3. 注册 router 对象,为 vue 实例注入:
  15. // $route:存储了一些路由规则,包括当前的全路径,相对路径,组件名,传入参数
  16. // $router: VueRouter 实例对象,提供一些路由相关的方法 比如常用的 push replace go,以及当前的路由模式mode(hash|history),当前路由的规则 currentRoute
  17. router,
  18. render: h => h(App)
  19. }).$mount('#app')

VueRouter 的类图:

  1. VueRouter
  2. ----------------
  3. options
  4. data
  5. routeMap
  6. Constructor(Options): VueRouter
  7. install(Vue): void
  8. init(): void
  9. initEvent(): void
  10. createRouteMap(): void
  11. initComponents(Vue): void

实现:

使用 vue-cli创建的 Vue 项目,Vue 默认是运行时版本,它不包含编译器,template无法使用,需要通过配置vue.config.js,将runtimeCompiler设为 true,启用运行时编译器。如果不使用 template, 也可以选择手动实现组件的 render 函数。

History 模式

  1. console.dir(Vue)
  2. let _Vue = null
  3. class VueRouter {
  4. static install(Vue){
  5. //1 判断当前插件是否被安装
  6. if(VueRouter.install.installed){
  7. return;
  8. }
  9. VueRouter.install.installed = true
  10. //2 把Vue的构造函数记录在全局
  11. _Vue = Vue
  12. //3 把创建Vue的实例传入的router对象注入到Vue实例
  13. // _Vue.prototype.$router = this.$options.router
  14. _Vue.mixin({
  15. beforeCreate(){
  16. if(this.$options.router){ // 每个组件都是一个Vue实例,这里只需要给 new Vue() 挂载 $router 属性,所以要通过这个属性判断,因为组件的 $options 没有 router 属性。
  17. _Vue.prototype.$router = this.$options.router
  18. }
  19. }
  20. })
  21. }
  22. constructor(options){
  23. this.options = options
  24. this.routeMap = {}
  25. // observable
  26. this.data = _Vue.observable({ // 创建响应式对象
  27. current:"/"
  28. })
  29. this.init()
  30. }
  31. init(){
  32. this.createRouteMap()
  33. this.initComponent(_Vue)
  34. this.initEvent()
  35. }
  36. createRouteMap(){
  37. //遍历所有的路由规则 吧路由规则解析成键值对的形式存储到routeMap中
  38. this.options.routes.forEach(route => {
  39. this.routeMap[route.path] = route.component
  40. });
  41. }
  42. initComponent(Vue){
  43. Vue.component("router-link",{
  44. props:{
  45. to:String
  46. },
  47. render(h){
  48. return h("a",{
  49. attrs:{
  50. href:this.to
  51. },
  52. on:{
  53. click:this.clickhander
  54. }
  55. },[this.$slots.default])
  56. },
  57. methods:{
  58. clickhander(e){
  59. history.pushState({},"",this.to)
  60. this.$router.data.current=this.to
  61. e.preventDefault()
  62. }
  63. }
  64. // template:"<a :href='to'><slot></slot><>"
  65. })
  66. const self = this
  67. Vue.component("router-view",{
  68. render(h){
  69. // self.data.current
  70. const cm=self.routeMap[self.data.current]
  71. return h(cm)
  72. }
  73. })
  74. }
  75. // 实现浏览器前进后退按钮
  76. initEvent(){
  77. //
  78. window.addEventListener("popstate",()=>{
  79. this.data.current = window.location.pathname
  80. })
  81. }
  82. }

Hash 模式

  1. import Vue from 'vue'
  2. console.dir(Vue)
  3. let _Vue = null
  4. export default class VueRouter {
  5. // 实现 vue 的插件机制
  6. static install(Vue) {
  7. //1 判断当前插件是否被安装
  8. if (VueRouter.install.installed) {
  9. return;
  10. }
  11. VueRouter.install.installed = true
  12. //2 把Vue的构造函数记录在全局
  13. _Vue = Vue
  14. //3 把创建Vue的实例传入的router对象注入到Vue实例
  15. // _Vue.prototype.$router = this.$options.router
  16. // 混入
  17. _Vue.mixin({
  18. beforeCreate() {
  19. if (this.$options.router) {
  20. _Vue.prototype.$router = this.$options.router
  21. }
  22. }
  23. })
  24. }
  25. // 初始化属性
  26. constructor(options) {
  27. this.options = options // options 记录构造函数传入的对象
  28. this.routeMap = {} // routeMap 路由地址和组件的对应关系
  29. // observable data 是一个响应式对象
  30. this.data = _Vue.observable({
  31. current: "/" // 当前路由地址
  32. })
  33. this.init()
  34. }
  35. // 调用 createRouteMap, initComponent, initEvent 三个方法
  36. init() {
  37. this.createRouteMap()
  38. this.initComponent(_Vue)
  39. this.initEvent()
  40. }
  41. // 用来初始化 routeMap 属性,路由规则转换为键值对
  42. createRouteMap() {
  43. //遍历所有的路由规则 把路由规则解析成键值对的形式存储到routeMap中
  44. this.options.routes.forEach(route => {
  45. this.routeMap[route.path] = route.component
  46. });
  47. }
  48. // 用来创建 router-link 和 router-view 组件
  49. initComponent(Vue) {
  50. // router-link 组件
  51. Vue.component('router-link', {
  52. props: {
  53. to: String
  54. },
  55. // render --- 可在 vue 运行时版直接使用
  56. render(h) {
  57. // h(选择器(标签的名字), 属性,生成的某个标签的内容)
  58. return h('a', {
  59. attrs: {
  60. href: '#' + this.to,
  61. },
  62. // 注册事件
  63. // on: {
  64. // click: this.clickHandler // 点击事件
  65. // },
  66. }, [this.$slots.default]) // this.$slot.default 默认插槽
  67. },
  68. });
  69. // router-view 组件
  70. const self = this; //这里的 this 指向 vueRouter 实例
  71. Vue.component('router-view', {
  72. render(h) {
  73. // 根据 routerMap 中的对应关系,拿到当前路由地址所对应的组件
  74. const component = self.routeMap[self.data.current]
  75. return h(component)
  76. }
  77. })
  78. }
  79. // 用来注册 hashchange 事件
  80. initEvent () {
  81. window.addEventListener('hashchange', () => {
  82. this.data.current = this.getHash();
  83. });
  84. window.addEventListener('load', () => {
  85. if (!window.location.hash) {
  86. window.location.hash = '#/';
  87. }
  88. });
  89. }
  90. getHash() {
  91. return window.location.hash.slice(1) || '/';
  92. }
  93. }