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