preface

Personal learning process records, convenient follow-up review, if there are mistakes or better methods, I hope the boss pointed out

Route hop process

Page URL changes (click the browser arrow, enter the address directly, or depending on the mode, Such as the history through the window. The history. PushState, hash pattern through the location. The hash = XXXX to change) by listeners listen to (window. AddEventListener (” popstate “, the function () {}) or window.onhashchange=function(){}). PushState does not trigger a popState event

files



Hash and history have methods in common, so write a base class, and they can both inherit base

The routes. Js class uses Router and the index.js class uses Router

Routertables and Routers

RouterView and RouterLink should be global components. Vue.use() requires an install method on the Router. RouterTable stores the routing table. HistoryMode is the relative method in history mode.

import HistoryMode from "./mode/history"; import Vue from "vue"; import RouterView from "./components/RouterView.vue"; import RouterLink from "./components/RouterLink.vue"; Vue.component("RouterView", RouterView); Vue.component("RouterLink", RouterLink); class RouterTable { constructor(routes) { this.pathMap = new Map() this.init(routes) } init(routes) { const addRoute = (route) => { this.pathMap.set(route.path, route) } routes.forEach(route => addRoute(route)) } math(path) { let find = this.pathMap.get(path) if (find) return find  } } export default class Router { constructor({ routes = [] }) { this.routerTable = new RouterTable(routes) this.history = new HistoryMode() } } Router.install = () => { Vue.mixin({ beforeCreate() { }, }); };Copy the code

Similarities and differences between history and Hash modes

The base hash history file is a base hash history file. The base hash history file is a base hash history file. The base hash history file is a base hash history file.

HistoryMode basis

So first of all, an instance of HistoryMode needs to call a listener method, and if the URL changes, you need to get the URL, The _route transitionTo method is inherited from BaseMode. This method can be directly written in BaseMode

import BaseMode from "./base"; export default class HistoryMode extends BaseMode { constructor(options) { super(options); This.initlistener ()} // Listen initListener() {window.addeventListener (' popState ', () => {this.transitionto (this.getCurrentLocation())})} // getCurrentLocation() {let path = window.location.pathname return path + window.location.search + window.location.hash } }Copy the code

BaseMode basis

If you want to change the _route on the router instance when the URL changes, you can call the init method on the router instance while installing, pass in a callback, and then change the _route on the router instance when calling transitionTo

Export default class BaseMode {// This. History = new HistoryMode() on the Router HistoryMode(this) constructor(router) { this.routerTable = router.routerTable } listen(cb) { this.cb = cb } TransitionTo (route) {// Obtain route information from path before switching. Then change '_route' this.current =this.routerTable.math(route) this.cb(this.current)}}Copy the code

Init method in the Router class

  init(app) {
    const { history } = this
    history.listen((route) => {
      app._route = route
    })
  }
Copy the code

install

_routerRoot and _router are the same for all Vue components

The vue Router instance calls init to initialize

Router.install = () => { Vue.mixin({ beforeCreate() { if (this.$options.router ! == undefined) {this._routerRoot = this // Allows components to access vue instances and things above, Router this._router. Init (this) vue.util. DefineReactive (this, '_route', this._router.history.current) } else { this._routerRoot = this.$parent && this.$parent._routerRoot || this } }, }); };Copy the code

RouterView

The rener function gets the current routing information to render the page

<script> export default { name: "RouterView", render() { const route = this._routerRoot._route; if (! route) return; const hook = { init(vnode) { route.instance = vnode.componentInstance; }}; const { component } = route; return <component hook={hook} />; }}; </script>Copy the code

Change the URL display directly

At this point the route is starting to take shape and can be displayed by changing the URL

RouterLink

The next step is to change it via RouterLink, and then write the push method on the Router

<template> <div> <a style="cursor: pointer" @click="jump"> <slot></slot> </a> </div> </template> <script> export default { props: { to: { type: String, required: true, }, }, data() { return { test: "123", }; }, methods: { jump(to) { const router = this._routerRoot._router; router.push(this.to); ,}}}; </script>Copy the code

The Router supplement

  push(to) {
    this.history.push(to)
  }
Copy the code

HistoryMode supplement

  push(target) {
    this.transitionTo(target)
    window.history.pushState({ key: +new Date() }, "", target)
  }
Copy the code

Complete demo

At this point, the low-profile route for history mode has been written

HashMode

The Router also needs to be modified a little bit, so no code will be posted here. It can be optimized to pass in mode as a parameter and perform different operations for different modes

import BaseHistory from "./base";
export default class HashHistory extends BaseHistory {
  constructor(options) {
    super(options)
    this.initListener()
  }
  initListener() {
    window.onhashchange = () => {
      this.transitionTo(this.getCurrentLocation())
    }
  }
  getCurrentLocation() {
    let path = decodeURI(window.location.hash) || "#/";
    let address = path.split('#')[1]
    //console.log(address, 'path')
    return address;
  }
  push(target) {
    this.transitionTo(target);
    window.location.hash = target
  }
}
Copy the code

Route navigation guard

Global guard

BeforeEach ((to, from, next) => {console.log("router.beforeEach", to, from); next(); }); Router.beforeresolve ((to, from, next) => {console.log(" router.beforeresolve ", to, from); next(); }); AfterEach ((to, from) => {console.log(" router.aftereach ", to, from); });Copy the code

Exclusive route guard beforeEnter

{ path: "/", name: "Home", component: Home, beforeEnter: (to, from, next) => { console.log("/home.beforeEnter", to, from); next(); }},Copy the code

Component internal guard

The component class guard does not write its source code

  beforeRouteEnter(to, from, next) {
    console.log("home-beforeRouteEnter", to, from);
    next();
  },
  beforeRouteUpdate(to, from, next) {
    console.log("home-beforeRouteUpdate", to, from);
    next();
  },
  beforeRouteLeave(to, from, next) {
    console.log("home-beforeRouteLeave", to, from);
    next();
  },
Copy the code

Router

Write a function that is called when the global route guard is triggered. Store the functions that need to be executed in Hooks

function registerHook(list, fn) { list.push(fn); return () => { let i = list.indexOf(fn); if (i > -1) list.splice(i, 1); }; } export default class Router { constructor({ routes = [] }) { //...... this.beforeHooks = []; this.resolveHooks = []; this.afterHooks = []; } beforeEach(fn) { registerHook(this.beforeHooks, fn); } beforeResolve(fn) { registerHook(this.resolveHooks, fn); } afterEach(fn) { registerHook(this.afterHooks, fn); }}Copy the code

BaseMode

ConfirmTransition = confirmTransition = confirmTransition = confirmTransition = confirmTransition = confirmTransition = confirmTransition = confirmTransition = confirmTransition = confirmTransition = confirmTransition = confirmTransition = confirmTransition = confirmTransition = confirmTransition = confirmTransition = confirmTransition = confirmTransition AfterEach is executed, or onAbort is executed for the desired action

export default class BaseMode { constructor(router) { this.routerTable = router.routerTable; this.router = router; } listen(cb) { this.cb = cb; } transitionTo(target) { const route = this.routerTable.math(target); this.confirmTransition(route, () => { this.updateRoute(route); }); } confirmTransition(route, onComplete, onAbort) { if (route == this.current) return; // Since the parameters (functions) passed in by the routing navigation need to be executed sequentially, put them in an array in the order in which they are executed. Use a similar const iterator to a call queue = [...... This router beforeHooks, route beforeEnter,... this. The router. ResolveHooks,]; runQueue(queue, iterator, () => onComplete()); } updateRoute(route) { let from = this.current; this.current = route; this.cb(this.current); this.router.afterHooks.forEach((hook) => { hook && hook(this.current, from); }); }}Copy the code

runQueue

Queue is an array of arguments (functions) passed in by the navigation guard, iter is something like an iterator, and end is executed after all the functions in queue have finished, updating the route and rendering the page

export function runQueue(queue, iter, end) { const step = (index) => { if (index >= queue.length) { end(); } else { if (queue[index]) { iter(queue[index], () => { step(index + 1); }); } else { step(index + 1); }}}; step(0); }Copy the code

iterator

Hook is to keep up with the beforeEach argument (function), to is route(next page to jump to), from is this.current (left page, this.current has not been updated yet, so this is still the route to the last page, Next (next in beforeEach) is a callback that can pass false,error, etc. Only false is demonstrated here)

const iterator = (hook, next) => { hook(route,this.current, (to) => { if (to === false) { onAbort && onAbort(); } else { next(); }}); };Copy the code

The complete code

BaseMode

import { runQueue } from "../../util/async";
export default class BaseMode {
  constructor(router) {
    this.routerTable = router.routerTable;
    this.router = router;
  }
  listen(cb) {
    this.cb = cb;
  }
  transitionTo(target) {
    const route = this.routerTable.math(target);
    this.confirmTransition(route, () => {
      this.updateRoute(route);
    });
  }
  confirmTransition(route, onComplete, onAbort) {
    if (route == this.current) return;
    const queue = [
      ...this.router.beforeHooks,
      route.beforeEnter,
      ...this.router.resolveHooks,
    ];
    const iterator = (hook, next) => {
      hook(this.current, route, (to) => {
        if (to === false) {
          onAbort && onAbort();
        } else {
          next();
        }
      });
    };
    runQueue(queue, iterator, () => onComplete());
  }
  updateRoute(route) {
    let from = this.current;
    this.current = route;
    this.cb(this.current);
    this.router.afterHooks.forEach((hook) => {
      hook && hook(this.current, from);
    });
  }
}
Copy the code

The history and the hash

import BaseMode from "./base";

export default class HistoryMode extends BaseMode {
  constructor(options) {
    super(options);
    this.initListener()
  }
  initListener() {
    window.addEventListener('popstate', () => {
      this.transitionTo(this.getCurrentLocation())
    })
  }
  getCurrentLocation() {
    let path = window.location.pathname
    return path + window.location.search + window.location.hash
  }
  push(target) {
    this.transitionTo(target)
    window.history.pushState({ key: +new Date() }, "", target)
  }
}

Copy the code
import BaseHistory from "./base";
export default class HashHistory extends BaseHistory {
  constructor(options) {
    super(options)
    this.initListener()
  }
  initListener() {
    window.onhashchange = () => {
      this.transitionTo(this.getCurrentLocation())
    }
  }
  getCurrentLocation() {
    let path = decodeURI(window.location.hash) || "#/";
    let address = path.split('#')[1]
    //console.log(address, 'path')
    return address;
  }
  push(target) {
    this.transitionTo(target);
    window.location.hash = target
  }
}
Copy the code

Router

import HistoryMode from "./mode/history"; import Vue from "vue"; import RouterView from "./components/RouterView.vue"; import RouterLink from "./components/RouterLink.vue"; Vue.component("RouterView", RouterView); Vue.component("RouterLink", RouterLink); class RouterTable { constructor(routes) { this.pathMap = new Map(); this.init(routes); } init(routes) { const addRoute = (route) => { this.pathMap.set(route.path, route); }; routes.forEach((route) => addRoute(route)); } math(path) { let find = this.pathMap.get(path); if (find) return find; } } function registerHook(list, fn) { list.push(fn); return () => { let i = list.indexOf(fn); if (i > -1) list.splice(i, 1); }; } export default class Router { constructor({ routes = [] }) { this.routerTable = new RouterTable(routes); this.history = new HistoryMode(this); this.beforeHooks = []; this.resolveHooks = []; this.afterHooks = []; } init(app) { const { history } = this; history.listen((route) => { app._route = route; }); history.transitionTo(history.getCurrentLocation()); } push(to) { this.history.push(to); } beforeEach(fn) { registerHook(this.beforeHooks, fn); } beforeResolve(fn) { registerHook(this.resolveHooks, fn); } afterEach(fn) { registerHook(this.afterHooks, fn); } } Router.install = () => { Vue.mixin({ beforeCreate() { if (this.$options.router ! == undefined) { this._routerRoot = this; this._router = this.$options.router; this._router.init(this); Vue.util.defineReactive(this, "_route", this._router.history.current); } else { this._routerRoot = (this.$parent && this.$parent._routerRoot) || this; }}}); };Copy the code

RouerLink and routerView

<template> <div> <a style="cursor: pointer" @click="jump"> <slot></slot> </a> </div> </template> <script> export default { name: "RouterLink", props: { to: { type: String, required: true, }, }, data() { return { test: "123", }; }, methods: { jump() { const router = this._routerRoot._router; router.push(this.to); ,}}}; </script>Copy the code
<script> export default { name: "RouterView", render() { const route = this._routerRoot._route; if (! route) return; const { component } = route; return <component />; }}; </script>Copy the code

The effect

“Said

Low version of the routing source code to this end, part is my own understanding, there may be some mistakes, hope to see the big guy generous comments