This article aims to introduce the implementation ideas of vue-router and implement a simplified version of vue-router. Let’s first look at the most basic use of vue-router in general projects. You can see that four routing components are defined here. We just need to inject the router object into the root vue instance and use it.
import VueRouter from 'vue-router'; import Home from '@/components/Home'; import A from '@/components/A'; import B from '@/components/B' import C from '@/components/C' (VueRouter) export default new ({ // mode: 'history', routes: [ { path: '/', component: Home }, { path: '/a', component: A }, { path: '/b', component: B }, { path: '/c', component: C } ] })
vue-router provides two global components,router-view
androuter-link
, the former is used for placeholder for routing components, and the latter is used to jump to the specified route when clicked. In addition, the component can be internally passed this.$ ,this.$
Wait for the API to implement routing jump. This article will implement the above two global components and push and replace APIs. It supports params parameters when calling, and supports two modes: hash and history. It ignores the implementation of advanced functions such as other APIs, nested routing, asynchronous routing, abstract routing, and navigation guards. This helps to understand the core principles of vue-router. The final code of this article is not recommended to be used in a production environment, it is only used for one learning purpose. Let’s implement it step by step.
install implementation
Any vue plug-in needs to implement an install method. When calling the plug-in, it is calling the plug-in's install method. So what should be done by routing install? First of all, we know that we will use the new keyword to generate a router instance, just like the previous code instance, and then mount it to the root vue instance. As a global route, we of course need to get this router instance in each component. In addition, we use the global components router-view and router-link. Since install will receive the Vue constructor as an actual parameter, it is convenient for us to call to register the global component. Therefore, in install, we mainly do two things, mount router instances for each component, and implement themrouter-view
and router-link
Two global components. Here is the code:
const install = (Vue) => { if (this._Vue) { return; }; ({ beforeCreate() { if (this.$options && this.$) { this._routerRoot = this; this._router = this.$; (this, '_routeHistory', this._router.history) } else { this._routerRoot = (this.$parent && this.$parent._routerRoot) || this } (this, '$router', { get() { return this._routerRoot._router; } }) (this, '$route', { get() { return { current: this._routerRoot._routeHistory.current, ...this._routerRoot._router.route }; } }) } }); ('router-view', { render(h) { ... } }) ('router-link', { props: { to: String, tag: String, }, render(h) { ... } }) this._Vue = Vue; }
This here represents the vue-router object. It has two properties exposed for external calls, one is install and the other is the Router constructor, which can ensure the correct installation of the plug-in and the instantiation of the routing. Let's first ignore the Router constructor and look at install . This._Vue in the above code is an attribute that was not defined at the beginning. Its purpose is to prevent multiple installations. We use the beforeCreate hook of each component to perform global mixing, with the purpose of letting each component instance share a router instance, that is, get the routing instance through this.$router and get the routing state through this.$route. This line of code needs to be focused on:
(this, '_routeHistory', this._router.history)
This line of code uses vue's responsive principle to register a _routeHistory property for the root vue instance, pointing to the history object of the routing instance, so history becomes responsive. Therefore, once the routed history changes, the component using this value will trigger the render function to re-render, and the component here is router-view. From this we can see a basic idea of vue-router implementation. In the above code, for the implementation of the render function of the two global components, we will rely on the router object, let's put it first and then implement them later. Let's analyze the Router constructor below.
Router constructor
After the analysis just now, we know that the router instance needs to have a history object, an object that holds the current routing state, and obviously it also needs to accept routes. According to routes, a route mapping table routerMap is needed to implement component search. A variable mode is also needed to determine what mode the routing is. Two APIs, push and replace are needed, the code is as follows:
const Router = function (options) { = ; // Store routing configuration = || 'hash'; = (null), // Generate routing status = createMap() // Generate routing table = new RouterHistory(); // Instantiate the routing historical object (); // Initialization} = (options) => { ... } = (options) => { ... } = () => { ... }
Let's take a look at the implementation of the routing table routerMap. Since nesting and other situations are not considered, the implementation is very simple, as follows:
const createMap = (routes) => { let resMap = (null); (route => { resMap[route['path']] = route['component']; }) return resMap; }
The implementation of RouterHistory is also very simple. According to the previous analysis, we only need a current attribute, as follows:
const RouterHistory = function (mode) { = null; }
With routing tables and history, the implementation of router-view is easy, as follows:
('router-view', { render(h) { let routerMap = this._self.$; return h(routerMap[this._self.$]) } })
This here is a renderProxy instance. It has a property _self that can get the current component instance and then access routerMap. You can see that the current of the routing instance history is essentially the path in the routing table we configured.
Next, let’s take a look at what initialization work Router needs to do. For hash routing, a hash value change on the url will not cause a page to refresh, but a hashchange event can be triggered. Since the route is initially null, no route can be matched, so the page refresh will not load any routing components. Based on these two points, in the init method, we need to implement listening for page loading completion and hash changes. For history routing, in order to accurately render the corresponding components when the browser advances and backs, a popstate event must also be listened to. The code is as follows:
= function () { if ( === 'hash') { fixHash() ('hashchange', () => { = getHash(); }) ('load', () => { = getHash(); }) } if ( === 'history') { removeHash(this); ('load', () => { = ; }) ('popstate', (e) => { if () { = ; } }) } }
When hash mode is enabled, we need to detect whether there is a hash value on the url. If not, force a default path. When hash routing, the route table will be found based on the hash value as the key. The fixHash and getHash implementations are as follows:
const fixHash = () => { if (!) { = '/'; } } const getHash = () => { return (1) || '/'; }
In this way, when refreshing the page and hash changes, current can get assignment and update, and the page can accurately render the route according to the hash value. The same goes for the history mode, but it passesAs a key search routing component, the history mode needs to remove hash that may exist on the URL, and the removeHash implementation is as follows:
const removeHash = (route) => { let url = ('#')[1] if (url) { = url; ({}, null, url) } }
We can see that when the browser backs, the history mode will trigger the popstate event. At this time, the path is obtained through the state state. So where does the state come from? The answer is from the pushState and replaceState of the object. These two methods can be used to implement the push method and replace method of the router. Let's take a look at their implementation here:
= (options) => { = ; if ( === 'history') { ({ path: }, null, ); } else if ( === 'hash') { = ; } = { ... } } = (options) => { = ; if ( === 'history') { ({ path: }, null, ); } else if ( === 'hash') { (`#${}`) } = { ... } }
pushState and replaceState can change the value of the url but do not cause the page to refresh, so that it will not cause a new request to occur. pushState will generate a history while replaceState will not, which will just replace the current url. When these two methods are executed, the path is stored in state, which allows the path to be obtained when popstate is triggered and component rendering is triggered. We call it in the component as follows, and will write params to the route property of the router instance, so that it can pass in the jumped component B this.$
You can access the parameter transfer.
this.$({ path: '/b', params: { id: 55 } });
router-link implementation
The implementation of router-view is very simple, as mentioned before. Finally, let’s take a look at the implementation of router-link and put the code first:
('router-link', { props: { to: String, tag: String, }, render(h) { let mode = this._self.$; let tag = || 'a'; let routerHistory = this._self.$; return h(tag, { attrs: tag === 'a' ? { href: mode === 'hash' ? '#' + : , } : {}, on: { click: (e) => { if ( === ) { (); return; } = ; switch (mode) { case 'hash': if (tag === 'a') return; = ; break; case 'history': ({ path: }, null, ); break; default: } (); } }, style: { cursor: 'pointer' } }, this.$) } })
router-link can accept two attributes, to represent the route path to jump, and tag represents the tag name to render by router-link, and defaults to the tag. If it is a tag, we add a href attribute to it. We bind the click event to the tag. If we detect that this jump is transferred to the current route, we will return nothing directly, and prevent the default behavior. Otherwise, we will change the route according to to. In hash mode and when it is a tag, you can directly use the browser's default behavior to complete the replacement of hash on the url, otherwise you can assign a value again. In history mode, use pushState to update the url.
The above implementation is a simple vue-router, see the complete codevue-router-simple 。
Summarize
The above is a detailed explanation of the simplified version of the vue-router implementation ideas introduced to you by the editor. I hope it will be helpful to you. If you have any questions, please leave me a message and the editor will reply to you in time. Thank you very much for your support for my website!