vue 路由实现原理

路由分为前端路由和后端路由,最初呢,是有后端路由,但是每次路由切换的时候都需要去刷新页面,发送请求,服务端返回数据,用户体验不好,后来就出现了前端路由,特点是浏览器不会重新刷新

前端路由分为两种,hash 模式 和 history 模式

history路由原理

  1. 首次进入项目,得解析url,加载对应的路由

  2. 项目已经加载了,需要切换路由。如果使用a标签切换,需要阻止默认事件,如果使用js的push方法切换,直接操作:

    1. window.history.pushState: 在浏览历史记录中增加一条记录
    2. window.history.replaceState: 把当前浏览地址换成replaceState 之后的地址,浏览记录总长度不变
    3. window对象中有一个onpopstate方法来监听历史栈的改变,只要有改变,就会触发该事件
    4. h5中提供的以上方法不会刷新浏览器

history原理图.png

代码实现:

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

Vue.config.productionTip = false

new Vue({
  router: router,
  render: h => h(App)
}).$mount('#app')
router.js
import Vue from 'vue'
import VueRouter from './vue-router'
import Home from './views/Home.vue'

Vue.use(VueRouter)

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

const router = new VueRouter({
  routes
})

export default router
vue-router文件夹下的 index.js
let Vue = null;

export default class Router{
  static install(_Vue){
    Vue = _Vue;
    
    Vue.mixin({
      beforeCreate(){
        if(this.$options.router){
          //根实例挂载了router
          // 路由初始化
          this.$options.router.init();
        }
        if(this.$root.$options.router){
          this.$router = this.$root.$options.router;
        }
      }
    })

  }

  constructor(options){
    this._options = options;
    this._routeMap = {};
    this._vm = new Vue({
      data: {
        currentPath : '/'
      }
    })
  }

  init(){
    // 解析路由表
    this.getRouteMap();
    // 注册组件
    this.initComponent();
    // 解析第一次的路由
    this.loadRoute();
    // 监听返回的事件,重新装载组件
    this.onBack();
  }

  getRouteMap(){
    this._options.routes.forEach(route=>{
      this._routeMap[route.path] = route;
    })
  }

  initComponent(){
    const self = this;
    Vue.component('router-view', {
      render(h){
        const path = self._vm.currentPath;
        const component = self._routeMap[path] && self._routeMap[path].component;
        return h(component);
      }
    });
    Vue.component('router-link', {
      props: {
        to: String
      },
      render(h){
        return h('a', {
          class: {
            'router-link-exact-active': self._vm.currentPath === this.to
          },
          attrs: {
            href: this.to
          },
          on: {
            // a标签点击事件
            click: (ev)=>{
              // 阻止默认事件
              ev.preventDefault();
              // 修改地址栏
              window.history.pushState({}, '', this.to);
              // 装载组件
              self._vm.currentPath = this.to;
            }
          }
        }, this.$slots.default);
      }
    });
  }


  loadRoute(){
    window.addEventListener('load', ()=>{
      //得到路由的地址
      let path = window.location.pathname;
      // 加载路由对应的组件
      this._vm.currentPath = path;
    })
  }

  onBack(){
    window.addEventListener('popstate', ()=>{
      //得到路由的地址
      let path = window.location.pathname;
      // 加载路由对应的组件
      this._vm.currentPath = path;
    })
  }


  push(path){
    // 修改地址栏
    window.history.pushState({}, '', path);
    // 加载路由对应的组件
    this._vm.currentPath = path;
  }

  back(){
    window.history.back();
  }

  forward(){
    window.history.forward();
  }

  go(n){
    window.history.go(n);
  }

}

hash 路由的原理

地址栏变化,路由要切换,需要监听onhashchange
js切换路由,实际上是修改地址栏的hash值

  1. 改变url后面的hash 值,不会向服务器发送请求,因此也就不会刷新页面,每次hash值改变,触发hashchange 事件,因此我们可以通过监听该事件,来知道hash值发生了哪些变化(location.hash)
  2. 改变hash 不会触发页面跳转,hash 链接是当前页面中的某个片段,那么页面会滚动到对应位置,如果没有,则没有任何效果

hash原理图.png

代码实现:
main.js

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

Vue.config.productionTip = false

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

router.js

import Vue from 'vue'
import VueRouter from './vue-router'
import Home from './views/Home.vue'

Vue.use(VueRouter)

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

const router = new VueRouter({
  routes
})

export default router

vue-router 文件夹下的 index.js

let Vue = null;

export default class Router{

  static install(_Vue){
    Vue = _Vue;

    Vue.mixin({
      beforeCreate(){
        if(this.$options.router){
          //只有vm实例,而且是配置了router的vm实例,会进来
          this.$options.router.init(); 
        }
        if(this.$root.$options.router){
          this.$router = this.$root.$options.router;
        }
      }
    });

  }


  constructor(options){
    this._options = options;
    
    this._routeMap = {};

    this._routeEach = [];

    this._vm = new Vue({
      data: {
        //保存当前路由所在的地址
        currentPath: '/'
      }
    });

  }

  init(){
    // 解析路由表
    this.getRouteMap();
    
    // 配置组件
    this.initComponent();
    
    // 加载路由
    this.addEvents();
  }

  // 解析路由表
  getRouteMap(){
    this._options.routes.forEach(route=>{
      this._routeMap[route.path] = route;
    });
  }

  initComponent(){
    let self = this; 
    Vue.component('router-view', {
      render(h){
        let currentPath = self._vm.currentPath;
        let component = self._routeMap[currentPath].component;
        return h(component);
      }
    });
    Vue.component('router-link', {
      props: {
        to: String
      },
      render(h){
        return h(
          'a',
          {
            class: {
              'router-link-exact-active': self._vm.currentPath === this.to
            },
            attrs: {
              href: '#'+this.to
            }
          },
          this.$slots.default
        )
      }
    });
  }

  // 加载路由
  addEvents(){
    window.addEventListener('hashchange', this.onHashChange.bind(this));
    window.addEventListener('load', this.onHashChange.bind(this));
  }

  getPath(){
    return window.location.hash.slice(1);
  }


  // 处理路由
  onHashChange(ev){
    console.log('onHashChange函数调用了.....');
    

    //调用路由拦截
    if(this._routeEach.length > 0){
      let {newURL, oldURL} = ev;
      let to = {};
      let from = {};
      if(oldURL){
        // hashChange事件
        // 重新设置currentPath,那么router-view就会重新装载
        to = {
          path: newURL.split('#')[1]
        }
        from = {
          path: oldURL.split('#')[1]
        }
      }else{
        //load事件
        to = {
          path: window.location.hash.split('#')[1]
        }
        from = {
          path: null
        }
      }

      this.runEach(to, from);
      
    }
    //没有拦截
    else{
      let path = this.getPath();
      let route = this._routeMap[path];
      this._vm.currentPath = route.path;
    }
  }

  // 切换路由
  push(path){
    // this._vm.currentPath = path;
    window.location.hash = '#'+path;
  }

  // 路由拦截
  beforeEach(cb){
    this._routeEach.push(cb);
  }

  //执行路由拦截
  runEach(to, from, index = 0){
    console.log(index);
    this._routeEach[index](to, from, ()=>{
      index++;
      if(index < this._routeEach.length){
        this.runEach(to, from, index);
      }else{
        let path = this.getPath();
        let route = this._routeMap[path];
        this._vm.currentPath = route.path;
      }
    });
  }

}

history 路由 和 hash 路由的区别:

  1. hash 只能修改#后面的,而且hash值必须和原先的值不一样
  2. pushState 能修改路径,查询参数和片段标识符,pushState比hash更符合前端路由的访问方式,更加优雅,但是history模式需要后端配合
发布了13 篇原创文章 · 获赞 22 · 访问量 390

猜你喜欢

转载自blog.csdn.net/weixin_44691775/article/details/104424612
今日推荐