vue的路由的原理(自己封装一个vue-router插件)

vue的路由的原理

  • 前言:
    • 路由实例化:
    • 路由匹配:
    • 路由跳转:
    • 路由钩子:
    • 插件调用install方法
    • 封装RouterView
    • 封装RuoterLink
    • 详细步骤
      • main.js
      • \src\router\index.js
      • \src\plugins\router.js
      • \src\plugins\components\RouterView.js
      • \src\plugins\components\RouterLink.js

前言:

  • Vue Router 是 Vue.js 官方的路由管理器,它和 Vue.js 的核心深度集成,可以非常方便地实现单页应用程序的路由功能
  • 两种模式:hash模式和history模式
  • hash模式实现基础步骤:
    • vueRouter作为一个插件 ,需要实现install方法,install方法中接收两个参数,一个是vue构造函数,另一个是使用**vue.use()**的时候传递的参数。
    • 然后我们去监听onHashchange事件的变化,当hash值发生变化时,匹配对应的组件。这个时候我们需要封装一个match方法,循环routes(routes当中存放组件和路径的对应关系),拿到所有组件和路径
    • 定义一个响应式对象,代表当前路径,判断当前路径和循环的路径是否相等,并且是否包含,如果满足条件就push到数组当中
      所有的组件都存到数组中去,onHashchange发生变化时,把组件都清空,在继续调用match方法
    • 这个时候进行router-view,对被routerView渲染的组件进行标记为true
    • 在app组件中被routerView渲染机组件层级初始值标记为0
    • 找到当前组件的父组件
    • 对父组件进行循环,知道找到app组件,再循环中判断当前的子组件是否为routerView组件,如果是,层级**+1**
    • 定义存储组件的变量,找到对应组件赋值

路由实例化:

Vue Router 的核心是一个 Vue 插件,通过调用 Vue.use(VueRouter) 方法来实例化一个路由对象。在 VueRouter 的构造函数中,会初始化一些基本属性和方法,如 options、history、current、match 等。

 constructor(options) {
    this.$options = options;
    Vue.util.defineReactive(this, 'matched', [])
    const url = location.hash.slice(1) || '/'
    // 定义一个响应式的current属性
    Vue.util.defineReactive(this, 'current', url);

    this.init();
  }
   // 初始化函数
  init() {
    this.match();
    this.bindEvent();
  }

路由匹配:

Vue Router 的路由匹配是通过 match 方法实现的。match 方法接收一个 route 对象,返回一个匹配的路由记录。在 match 方法中,会遍历路由表,根据路由表中的 path 和 route.path 进行匹配,如果匹配成功,则返回对应的路由记录。
bindEvent方法绑定hashchange事件,监听hash值的变化

match(routes) {
    routes = routes || this.$options.routes;
    
    routes.forEach(route => {
      // 处理路由完全匹配(根路径)
      if(route.path === '/' && this.current.includes(route.path)) {
        this.matched.push(route)
  
      }
      // /course/vue
      if(route.path !== '/' && this.current.includes(route.path)) {
        this.matched.push(route);
        if(route.children) {
          this.match(route.children)
        }
      }
    })
  }
 // 绑定hashchange事件,监听hash的变化
  bindEvent() {
    // 监听hash的变化
    addEventListener('hashchange', () => {
      // 当hash值改变时,将最新的hash值赋值给current
      this.current = location.hash.slice(1) || '/'
      // 清空路由数组
      this.matched = [];
      this.match();
    })
  }

路由跳转:

Vue Router 的路由跳转是通过 push、replace、go、back 等方法实现的。在这些方法中,会调用 history 对象的 pushState、replaceState、go、back 等方法,实现浏览器的前进后退和地址栏的变化。

路由钩子:

Vue Router 的路由钩子是通过 beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave 等方法实现的。在这些方法中,可以进行路由拦截、权限控制、数据预取等操作。

 Vue.mixin({
    beforeCreate() {
      if(this.$options.router) {
      }
    }
  })

插件调用install方法

VRouter.install = function(_Vue) {
  Vue = _Vue;

  Vue.mixin({
    beforeCreate() {
      if(this.$options.router) {
        // 这样做就可以通过组件的实例访问到router对象
        Vue.prototype.$router = this.$options.router;
      }
    }
  })

  // 注册router-link和router-view两个全局组件
  Vue.component('router-link', RouterLink)
  Vue.component('router-view', RouterView)
}

封装RouterView

  • 对被routerView渲染的组件进行标记为true
  • 在app组件中被routerView渲染机组件层级初始值标记为0
  • 找到当前组件的父组件
  • 对父组件进行循环,知道找到app组件,再循环中判断当前的子组件是否为routerView组件,如果是,层级**+1**
  • 定义存储组件的变量,找到对应组件赋值
export default {
  render(h) {
    // 对当前的routerView组件进行标记(即被routerView渲染的组件)
    this.$vnode.data.routerView = true;
    // 路由嵌套层级计算(在App组件中被routerView渲染的组件层级为0)
    let depath = 0;
    // 找到当前组件的父组件(在App组件中被routerView渲染的组件的父组件就是根组件App)
    let parent = this.$parent;
    
    // 循环父组件,一直找到App组件退出循环
    while(parent) {
      // 一直往下找子组件
      const vnodeData = parent.$vnode && parent.$vnode.data;
      if(vnodeData) {
        // 判断当前子组件是否是routerView组件,如果是让其层级+1
        if(vnodeData.routerView) {
          depath++;
        }
      }
      parent = parent.$parent;
    }

    // 定义存储组件的变量
    let componet = null;

    // 找到对应组件赋值
    const route = this.$router.matched[depath];
    if(route) {
      componet = route.component;
    }

    return h(componet)
  }
}

封装RuoterLink

export default {
  props: {
    to: {
      type: String | Object,
      required: true
    }
  },
  render(h) {
    return h('a', { attrs: { 'href': '#' + this.to } }, this.$slots.default)
  }
}

详细步骤

main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  router
}).$mount('#app')

\src\router\index.js

import Vue from 'vue';
import VueRouter from '../plugins/router'

Vue.use(VueRouter)

const router = new VueRouter({
  routes: [
    {
      path: '/home',
      component: () => import('../components/Home')
    },
    {
      path: '/cate',
      component: () => import('../components/Cate')
    },
    {
      path: '/course',
      component: () => import('../components/Course'),
      children: [
        {
          path: '/course/vue',
          component: () => import('../components/course/VueCourse')
        },
        {
          path: '/course/react',
          component: () => import('../components/course/ReactCourse')
        }
      ]
    }
  ]
})

export default router;

\src\plugins\router.js

import RouterLink from './components/RouterLink'
import RouterView from './components/RouterView'

let Vue;
class VRouter {
  constructor(options) {
    this.$options = options;
    Vue.util.defineReactive(this, 'matched', [])
    const url = location.hash.slice(1) || '/'
    // 定义一个响应式的current属性
    Vue.util.defineReactive(this, 'current', url);

    this.init();
  }
  // 初始化函数
  init() {
    this.match();
    this.bindEvent();
  }
  match(routes) {
    routes = routes || this.$options.routes;
    
    routes.forEach(route => {
      // 处理路由完全匹配(根路径)
      if(route.path === '/' && this.current.includes(route.path)) {
        this.matched.push(route)
  
      }
      // /course/vue
      if(route.path !== '/' && this.current.includes(route.path)) {
        this.matched.push(route);
        if(route.children) {
          this.match(route.children)
        }
      }
    })

    // console.log(this.matched);
  }
  // 绑定hashchange事件,监听hash的变化
  bindEvent() {
    // 监听hash的变化
    addEventListener('hashchange', () => {
      // 当hash值改变时,将最新的hash值赋值给current
      this.current = location.hash.slice(1) || '/'
      // 清空路由数组
      this.matched = [];
      this.match();
    })
  }
}

VRouter.install = function(_Vue) {
  Vue = _Vue;

  Vue.mixin({
    beforeCreate() {
      if(this.$options.router) {
        // 这样做就可以通过组件的实例访问到router对象
        Vue.prototype.$router = this.$options.router;
      }
    }
  })

  // 注册router-link和router-view两个全局组件
  Vue.component('router-link', RouterLink)
  Vue.component('router-view', RouterView)
}

export default VRouter;

\src\plugins\components\RouterView.js

export default {
  render(h) {
    // 对当前的routerView组件进行标记(即被routerView渲染的组件)
    this.$vnode.data.routerView = true;
    // 路由嵌套层级计算(在App组件中被routerView渲染的组件层级为0)
    let depath = 0;
    // 找到当前组件的父组件(在App组件中被routerView渲染的组件的父组件就是根组件App)
    let parent = this.$parent;
    
    // 循环父组件,一直找到App组件退出循环
    while(parent) {
      // 一直往下找子组件
      const vnodeData = parent.$vnode && parent.$vnode.data;
      if(vnodeData) {
        // 判断当前子组件是否是routerView组件,如果是让其层级+1
        if(vnodeData.routerView) {
          depath++;
        }
      }
      parent = parent.$parent;
    }

    // 定义存储组件的变量
    let componet = null;

    // 找到对应组件赋值
    const route = this.$router.matched[depath];
    if(route) {
      componet = route.component;
    }

    return h(componet)
  }
}

\src\plugins\components\RouterLink.js

export default {
  props: {
    to: {
      type: String | Object,
      required: true
    }
  },
  render(h) {
    return h('a', { attrs: { 'href': '#' + this.to } }, this.$slots.default)
  }
}

你可能感兴趣的:(vue.js,javascript,ecmascript)