Introduction to the

Vue-router is an official route manager provided by VUE. It is deeply integrated with the core of VUE, making it easy to build a single page.

Due to the popularity of Ajax technology, page no refresh has a better user experience, and gradually from the single page application routing, from the back end to the front end.

The routing process

  1. In Vue single-page architecture, ve-Router has a listener that listens for browser History changes.
  2. Typically, when the address in the browser address bar changes or the browser forward and back buttons are clicked, the History stack in History changes accordingly.
  3. When listening for a History change, vue-router matches the primary key against the route declared in the routing tablerouterViewComponent rendering

Issues related to

How do we listen for historical changes in the viewer?

// One implements HTML5Mode by listening for popState events on Windows
window.addEventListener("popstate".() = > {
  console.log(window.location.pathname);
});

// When the content after the url # changes and the page is not refreshed, the onHashchange function is triggered to implement HashMode
window.onhashchange = function() {
  console.log(location.hash);
};
Copy the code

Review vue-router usage

Basic usage

  1. Used to register and export routing tables
import Vue from "vue"; / / into the Vue
import VueRouter from "vue-router"; // If you do not import a router after installing it, it is equivalent to doing nothing
Vue.use(VueRouter); // Add VueRouter to vue
import index from "@/views/index/index.vue"; / / the index page
export default new VueRouter({
  // Expose our routing configuration file
  mode: "history".//history defaults to hash titles with # aim points
  routes: [
    // Route this stores the configuration file of our route
    {
      path: "/index".// The path to access the viewer
      name: "index".// This is the name we give the route
      component: index, // The corresponding component},]});Copy the code
  1. The main js import
new Vue({
  router,
  render: (h) = > h(App),
}).$mount("#app");
Copy the code

Dynamic routing

  1. Registered routing
export default new VueRouter({
  // Expose our routing configuration file
  mode: "history".//history defaults to hash titles with # aim points
  routes: [
    // Route this stores the configuration file of our route
    {
      path: "/index/:id".// The path to access the viewer
      name: "index".// This is the name we give the route
      component: index, // The corresponding component},]});Copy the code
  1. Using Dynamic Routing
<router-link to="/index/1">Jump to index 1 page</router-link>
<router-link to="/index/2">Jump to the Index 2 page</router-link>
Copy the code

Usage scenario: when the commodity details page, the page structure is the same, but the commodity ID is different, so this time you can use dynamic routing dynamic.

A dynamic route is a path followed by a /: ID

Dynamic routing means that component instances are reused and lifecycle hooks are not called repeatedly

Embedded routines by

  1. Registered routing
import Profile from "@/views/Profile/index.vue"; / / Profile page
export default new VueRouter({
  // Expose our routing configuration file
  mode: "history".//history defaults to hash titles with # aim points
  routes: [
    // Route this stores the configuration file of our route
    {
      path: "/index".// The path to access the viewer
      name: "index".// This is the name we give the route
      component: index, // The corresponding component
      children: [{// If /index/profile matches successfully,
          // The UserProfile will be rendered in User's 
      
          path: "profile".component: Profile,
        },
      ],
    },
  ],
});
Copy the code
  1. The actual application interface, usually composed of several layers of nested components, is usedchildrenTo declare nested routing components

The first is to implement HTML5Mode by listening for popState events on Windows

The other is that when the content after the url # changes and the page is not refreshed, the onHashchange function is triggered to implement HashMode

Routing guard

There are three types of navigational guard: global route guard, route exclusive guard, and component guard

Global guard Route exclusive Component internal guard
beforeEach beforeEnter beforeRouteEnter
beforeResolve beforeRouteUpdate
afterEach beforeRouteLeave

Implementation approach

Vue-router implementation requires at least two main lines:

  1. Routing relation association table — Listeners (two modes) — Render related components
  2. Navigation guard – global guard – Route exclusive guard – component guard

Line a

This main line is mainly from the route registration, jump aspects to explain, roughly explained the Vue single page system in the basic implementation of the route manager

Build the Router class to create the associated routing table

  1. Build the Router class,
    • Constructor:
      • Get the associated routing table,
      • Take an Html5Mode instance and inject the dependency,
    • The init method:
      • Listen for route changes and reassign values
      • Perform the first jump to the page
    • Push method:
      • The jump pagehistory.push
  2. Build the RouterTable class and build the routing table — Build the association of routes
    • Constructor:
      • Example Create a Map user cache association
      • Initialize the routes
    • The init method:
      • Traverses routes and adds the current route (addRoute) to the pathMap
      • Nested routines are handled recursively
    • The match method:
      • Matches whether the current path is in _pathMap
import Vue from "vue";
import RouterView from "./components/RouterView";
import RouterLink from "./components/RouterLink";

// Register RouterView and RouterLink globally
Vue.component("RouterView", RouterView); 
Vue.component("RouterLink", RouterLink);

// Build the routing table -- build the association of routes
class RouterTable {
  constructor(routes) {
    this._pathMap = new Map(a);// Associations are managed using map
    this.init(routes); // Initialize routes
  }
  init(routes) {
    // Add the current route to the pathMap
    const addRoute = route= > {
      this._pathMap.set(route.path, route);

      // If there are nested routines
      // if(route.children) {
        // Handle nesting for children forEach
      // }
    };

    // Run the addRoute command on each route
    routes.forEach(route= > addRoute(route));
  }

  // Matches whether the current path is in _pathMap
  match(path) {
    let find;
    for (const key of this._pathMap.keys()) {
      if (path === key) {
        find = key;
        break; }}return this._pathMap.get(find); }}import Html5Mode from "./history/html5";

// The class that constructs the route
export default class Router {
  constructor({ routes = [] }) {
    this.routerTable = new RouterTable(routes); // Build the routing table
    this.history = new Html5Mode(this); / / to monitor
  }
  init(app) {
    const { history } = this; // deconstruct history
    history.listen(route= > { // The vue.util. DefineReactive method is triggered by listening for route changes and reassigning values
      app._route = route;
    });
    // Perform the first jump to the page
    // history.transitionTo(history.getCurrentLocation());
  }
  push(to) {
    this.history.push(to); }}// Add your router to the vUE
Router.install = function() {
  Vue.mixin({
    beforeCreate() {
      if (this.$options.router ! = =undefined) {
        this._routerRoot = this;
        this._router = this.$options.router;
        this._router.init(this); // Inject a single application into the vUE page

        // Make your router responsive
        Vue.util.defineReactive(this."_route".this._router.history.current);
      } else {
        this._routerRoot = this.$parent && this.$parent._routerRoot; }}}); };Copy the code

Build listeners

The viewer mode is divided into HashMode and Html5Mode, and the listener is also divided into two kinds

Where there are similarities and differences in the history of the two models, a template pattern is used to build a base class

The base class contains logic for both modes. Hash stands for Hash Mode, and HTML5 stands for HTML5 Mode

The base base class

  1. Constructor to get the Router instance passed by the derived class. Dependency inversion, injecting the routerTable
  2. Listen:
    • Vue.use(Router) calls the router. install method
    • The router. install method injects the vue page into a single application (call init)
    • The Router init method is executed, calling history. listen and passing in the cb callback
    • The historyBase. listen method receives the cb callback and saves it
    • When the route is changed, the current route information is passed to the callback function cb. The callback function is executed to inject the route into the Vue single-page application.
  3. TransitionTo method:
    • When receiving the route, match the route in the routing table to find the route
    • Updating a route
// Include both hash Model and HTML5 Model logic
export default class HistoryBase {
  constructor(router) {
    this.router = router;
    this.routerTable = router.routerTable; // Dependency inversion to inject routerTable
  }

  // Modify the route's timing
  listen(cb) {
    this.cb = cb;
  }

  // Redirect the corresponding route
  transitionTo(target) {
    // Check whether the target is in the routing table
    const route = this.routerTable.match(target);

    // afterEach is triggered globally after render is executed after the route guard has completed the route update
    this.updateRoute(route);
  }

  updateRoute(route) {
    this.current = route;
    this.cb(this.current); }}Copy the code

Html5Mode

  1. Constructor: Receives arguments passed in during construction and passes them to the base class. Perform processing event listening
  2. Event listener handler (initListener): By listenerpopstateTo jump to the corresponding route
  3. Get Route method (getCurrentLocation): Returns the current full route path
  4. Route jump method (push): Manually jump routes and manually add a record to popState
import BaseHistory from "./base";

// represents the html5 Model inheriting from BaseHistory
export default class Html5History extends BaseHistory {
  constructor(options) {
    super(options);

    this.initListener(); // Handle event listening
  }

  initListener() {
    window.addEventListener("popstate".() = > {
      this.transitionTo(this.getCurrentLocation()); // Redirect the corresponding route
    });
  }

  // The two models get different routes
  getCurrentLocation() {
    let path = decodeURI(window.location.pathname) || "/";
    return path + window.location.search + window.location.hash;
  }

  push(target) {
    this.transitionTo(target); // Route to be manually transitionTo
    window.history.pushState({ key: +new Date()},"", target); // Add a record to popState}}Copy the code

RouterView

  1. Get the current route, return 404 page without route
  2. Deconstruct the current route and return it to the Component
  3. Register RouterView as a global component
<script> export default {name: "RouterView", render() {const route = this._routerroot._route; // If no route is matched, 404 pages will not be rendered, if necessary. route) { return; Const {component} = route; const {component} = route; return <component />; }}; </script>Copy the code

RouterLink

  1. Write a RouterLink component and add click events
  2. The props to parameter is received.
  3. Jump to the to route in the click event
  4. Register RouterLink as a global component
<template> <a class="link" @click="jump"> <! <slot></slot> </a> </template> <script> export default {name: "RouterLink", props: {to: {type: String, required: true}}, methods: {jump() {// Get router const router = this._routerroot._router; router.push(this.to); }}}; </script> <style scoped> .link { margin: 0 10px; text-decoration: underline; cursor: pointer; } </style>Copy the code

The effect

The main line 2

The first step is to figure out which navigational guards are triggered in certain scenarios.

  1. Bar jumps to /foo
    • BeforeRouteLeave Bar component leaves the guard
    • BeforeEach Global front-guard
    • BeforeRouteUpdate/Root component change guard
    • BeforeEnter Exclusive route guard
    • BeforeRouteEnter The guard that the foo component enters
    • BeforeResolve Global response guard
    • AfterEach is the afterEach global guard
  1. Page initialization is triggered
    • BeforeEach Global front-guard
    • BeforeEnter Exclusive route guard
    • The beforeRouteEnter component’s front guard
    • BeforeResolve Global response guard
    • AfterEach is the afterEach global guard

use

// router.js
import Vue from "vue";
import Router from "vue-router";
import Foo from "./pages/Foo";
import Bar from "./pages/Bar";

Vue.use(Router);

const router = new Router({
  routes: [{path: "/foo".component: Foo,
      beforeEnter(to, from, next) {
        BeforeResolve = router. BeforeResolve = router
        console.log("/foo::beforeEnter"); next(); }, {},path: "/bar".component: Bar },
  ],
});
/ / before parsing
router.beforeEach((to, from, next) = > {
  console.log("router.beforeEach");
  next();
});

/ / resolution
router.beforeResolve((to, from, next) = > {
  console.log("router.beforeResolve");
  next();
});

/ / resolved
router.afterEach((to, from) = > {
  console.log("router.afterEach", to, from);
});

export default router;
Copy the code
// foo.vue
<script>
export default {
  // Before component route resolution -- triggered after route exclusive guard
  beforeRouteEnter(to, from, next) {
    console.log("foo::beforeRouteEnter");
    next();
  },

  // Component routing is resolved when it changes
  beforeRouteUpdate(to, from, next) {
    console.log("foo::beforeRouteUpdate");
    next();
  },

  // The component route is resolved when it leaves
  beforeRouteLeave(to, from, next) {
    console.log("foo::beforeRouteLeave"); next(); }}; </script>Copy the code

Analysis:

The global routing guard receives a function, and each guard can appear more than once, requiring a queue for collection.

Collect global route guards

// The class that constructs the route
export default class Router {
  constructor(routes) {
    this.beforeHooks = []; // Route before hooks
    this.resolveHooks = []; // Route resolve hooks
    this.afterHooks = []; // Route after hooks
  }
  // Collection of global routing hooks
  beforeEach(fn) {
    return registerHook(this.beforeHooks, fn);
  }

  // Collection of global routing hooks
  beforeResolve(fn) {
    return registerHook(this.resolveHooks, fn);
  }

  // Collection of global routing hooks
  afterEach(fn) {
    return registerHook(this.afterHooks, fn); }}// Collect routes and destroy route hooks
function registerHook(list, fn) {
  list.push(fn);
  return () = > {
    const i = list.indexOf(fn);
    if (i > -1) list.splice(i, 1);
  };
}
Copy the code

Global routing guard queue, added three beforeHooks, resolveHooks, afterHooks routing queue

When the global routing guard is triggered, the corresponding routing guard is added to the corresponding queue and the function that can destroy the routing guard is returned

Listen to the History

As mentioned earlier, there are two modes of History, Hash Mode and Html5 Mode

  1. Hash Mode
import BaseHistory from "./base";
export default class HashHistory extends BaseHistory {
  constructor(options) {
    super(options);

    this.initListener();
  }
  initListener() {
    window.addEventListener(
      "hashchange".() = > {
        this.transitionTo(this.getCurrentLocation());
      },
      false
    );
  }

  // Redirect the corresponding route
  getCurrentLocation() {
    let href = window.location.hash;

    const searchIndex = href.indexOf("?");
    if (searchIndex < 0) {
      const hashIndex = href.indexOf("#");
      if (hashIndex > -1) {
        href = decodeURI(href.slice(0, hashIndex)) + href.slice(hashIndex);
      } else href = decodeURI(href);
    } else {
      href = decodeURI(href.slice(0, searchIndex)) + href.slice(searchIndex);
    }

    return href;
  }

  push(hash) {
    window.location.hash = hash; }}Copy the code

Listen for changes in History with hashchange and implement a function to get the current route.

The realization of push function, used for page jump

  1. Html5 Mode
import BaseHistory from "./base";

// represents the html5 Model inheriting from BaseHistory
export default class Html5History extends BaseHistory {
  constructor(options) {
    super(options);

    this.initListener(); // Handle event listening
  }

  initListener() {
    window.addEventListener("popstate".() = > {
      this.transitionTo(this.getCurrentLocation()); // Redirect the corresponding route
    });
  }

  // The two models get different routes
  getCurrentLocation() {
    let path = decodeURI(window.location.pathname) || "/";
    return path + window.location.search + window.location.hash;
  }

  push(target) {
    this.transitionTo(target); // Route to be manually transitionTo
    window.history.pushState({ key: +new Date()},"", target); // Add a record to popState}}Copy the code

Popstate listens for changes in History and implements a function to get the current route.

Popstate does not automatically add records; you need to manually add records

You also need to manually switch the logical transitionTo to the route specified

Implement the first jump

  1. Vue.use(Router)Register the router and trigger at the same timeRouter.installTo blend your router into the VUE
// router.js
Vue.use(Router) // Register the Router

// router/router.js
Router.install = function() {
  Vue.mixin({
    beforeCreate() {
      if (this.$options.router ! = =undefined) {
        this._routerRoot = this;
        this._router = this.$options.router;
        this._router.init(this); // Inject a single application into the vUE page

        // Use Vue's reactive functions to make your router responsive
        Vue.util.defineReactive(this."_route".this._router.history.current);
      } else {
        this._routerRoot = this.$parent && this.$parent._routerRoot; }}}); }; *`Router.install`- Vue instance has been mountedthis._routerRoot on - Trigger`router.init`Function and inject vUE - to turn the current routing information into responsiveCopy the code
  1. router.initThe function listens for route changes and reassigns a value to perform the first jump
export default class Router {
  init(app) {
    const { history } = this; // deconstruct history
    history.listen((route) = > {
      // The vue.util. DefineReactive method is triggered by listening for route changes and reassigning values
      app._route = route;
    });
    // Perform the first jump to the pagehistory.transitionTo(history.getCurrentLocation()); }}Copy the code

This function was introduced earlier and will not be repeated.

  1. HistoryBase.listenListener function, used to save the LISTEN callback function
export default class HistoryBase {
  constructor(router) {
    this.router = router;
    this.routerTable = router.routerTable; // Dependency inversion to inject routerTable
  }
  listen(cb) {
    this.cb = cb;
  }

  // Redirect the corresponding route
  transitionTo(target) {
    // Check whether the target is in the routing table
    const route = this.routerTable.match(target);

    // afterEach is triggered globally after render is executed after the route guard has completed the route update
    this.confirmTransition(route, () = > {
      this.updateRoute(route); }); }}Copy the code
  1. History. transitionTo Executes the jump function and receives the route

    • throughrouterTable.matchThe routable () function retrieves the corresponding routing information in the routing table
    • Performs route jump processing, such as page initialization of route guards
    • After the routing guard is completed, the afterEach global afterEach is triggered by updateRoute
  2. Initialize the routing guard HistoryBase. ConfirmTransition execution page

    • Skip to the current page
    • The front guards used to initialize the page form a queue
    • Execute all route guards in the queue
export default class HistoryBase {
  confirmTransition(route, onComplete, onAbort) {
    if (route === this.current) {
      return;
    }

    // page initializes routing task queue -- executes route guard
    const queue = [
      ...this.router.beforeHooks, // Execute global beforeEach first
      route.beforeEnter, // Exclusive guard route.beforeEnter
      route.component.beforeRouteEnter.bind(route.instance), // Component route beforeRouteEnter Note the direction of this. this.router.resolveHooks,// Execute global beforeResolve
    ];

    const iterator = (hook, next) = > {
      // hook (to, from, next) every route guard
      hook(route, this.current, (to) = > {
        if (to === false) {
          // Check whether the interrupt message exists
          onAbort && onAbort(to);
        } else{ next(to); }}); }; runQueue(queue, iterator,() = >onComplete()); }}// Execute the route guard queue
export function runQueue(queue, iter, end) {
  const step = (index) = > {
    if (index >= queue.length) {
      // Whether the execution is complete
      end();
    } else {
      if (queue[index]) {
        // Returns the current route of the iterator and the next function (to perform the next step)
        iter(queue[index], () = > {
          step(index + 1);
        });
      } else {
        step(index + 1); }}}; step(0);
}
Copy the code

Queue is the page initialization route task queue

Iterator is a specific task that executes a task queue, that is, each item of the routing guard and controls the routing guard’s execution

The clever design of runQueue ensures that the guard interrupts so that the routing guard can be controlled by next

  1. HistoryBase.updateRoutePerform an update route and trigger global afterEach
export function runQueue(queue, iter, end) {
    updateRoute(route) {
    const from = this.current;
    this.current = route;
    this.cb(this.current);

    // Global afterEach is executed after an update
    this.router.afterHooks.forEach(hook= > {
      hook && hook(route, from); }); }}Copy the code

Save the current route and change the current route to the forward route.

Notifies the listener to execute the callback function (cb).

Global afterEach is executed after an update

RouterView

<script>
export default {
  name: "RouterView".render() {
    // Get the current routing information
    const route = this._routerRoot._route;

    // The 404 page can be rendered if necessary
    if(! route) {return;
    }

    // Extract the Component from route by deconstructing it
    const { component } = route;
    // return <component />;

    // Assign vnode.componentInstance to this to ensure that this points when the component is guarded
    const hook = {
      init(vnode) {
        route.instance = vnode.componentInstance;
        // console.log("vnode", vnode);
        // console.log("instance", vnode.componentInstance);}};return (<component hook={hook} />); }}; </script>import Vue from "vue";
// Register the RouterView globally
Vue.component("RouterView", RouterView);
Copy the code

RouterLink

<template>
  <a class="link" @click="jump">
    <! -- Can render title -->
    <slot></slot>
  </a>
</template>
<script>
  export default {
    props: {
      to: {
        type: String.required: true,}},methods: {
      jump() {
        // Get the router
        const router = this._routerRoot._router;

        router.push(this.to); ,}}};</script>

<style scoped>
  .link {
    margin: 0 10px;
    text-decoration: underline;
    cursor: pointer;
  }
</style>
Copy the code
import Vue from "vue";
Vue.component("RouterLink", RouterLink);
Copy the code

The effect

The resources

The title link
The official documentation router.vuejs.org/zh/guide/
Vue – the router source code Github.com/vuejs/route…

conclusion

This article starts from the basic use of VUe-Router, starting from the two main lines, how to achieve a simple version of vue-Router expansion description

Vue-router is just a framework, the learning method in this article can be applied to many frameworks, of course, you also have a better learning method, also welcome to say from the comment section, together with growth.

This article is interested in personal gitee: gitee.com/wang_xi_lon…

I am Front-end Creek, welcome interested students to pay attention to the front-end Creek official account, also welcome to add me on wechat wXL-15153496335.