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
- In Vue single-page architecture, ve-Router has a listener that listens for browser History changes.
- 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.
- When listening for a History change, vue-router matches the primary key against the route declared in the routing table
routerView
Component 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
- 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
- The main js import
new Vue({
router,
render: (h) = > h(App),
}).$mount("#app");
Copy the code
Dynamic routing
- 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
- 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
- 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
- The actual application interface, usually composed of several layers of nested components, is used
children
To 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:
- Routing relation association table — Listeners (two modes) — Render related components
- 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
- 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 page
history.push
- The jump page
- Constructor:
- 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
- Constructor:
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
- Constructor to get the Router instance passed by the derived class. Dependency inversion, injecting the routerTable
- 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.
- 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
- Constructor: Receives arguments passed in during construction and passes them to the base class. Perform processing event listening
- Event listener handler (initListener): By listener
popstate
To jump to the corresponding route - Get Route method (getCurrentLocation): Returns the current full route path
- 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
- Get the current route, return 404 page without route
- Deconstruct the current route and return it to the Component
- 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
- Write a RouterLink component and add click events
- The props to parameter is received.
- Jump to the to route in the click event
- 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.
- 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
- 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
- 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
- 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
Vue.use(Router)
Register the router and trigger at the same timeRouter.install
To 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
router.init
The 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.
HistoryBase.listen
Listener 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
-
History. transitionTo Executes the jump function and receives the route
- through
routerTable.match
The 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
- through
-
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
HistoryBase.updateRoute
Perform 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.