In this article, you can learn:
- Implement your own Vue router
- Learn what Vue plug-ins are
Learning the notes made by the boss of station b and the implementation of the source code
Use the official Vue router
Initialize a project through Vue cli scaffolding
Download Vue router
PS: you can choose whether to install Vue router when generating Vue cli scaffold
The following is the manual installation process:
- After NPM installs Vue router, it is imported through import
- Then it is introduced through Vue.use()
- Then define a routing table routes
- Then new vueroter can get an instance
- Two new components, Home and About, have been created
Get code:
router/index.js
import Vue from 'vue' import Router from 'vue-router' import Home from '@/components/home' import About from '@/components/about' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'Home', component: Home }, { path: '/about', name: 'About', component: About } ] })
Import into main.js
import Vue from 'vue' import App from './App' import router from './router' Vue.config.productionTip = false new Vue({ el: '#app', router, components: { App }, template: '<App/>' })
Add this configuration item in new Vue
Using router link and router view
App.vue
<template> <div id="app"> <router-link to="/">home</router-link> <router-link to="/about">about</router-link> <router-view/> </div> </template>
effect:
Write a Vue router yourself
The old rule is to start with the source code first
Uncommented version:
let Vue; class VueRouter { constructor(options) { this.$options = options; let initial = window.location.hash.slice(1) || "/"; Vue.util.defineReactive(this, "current", initial); window.addEventListener("hashchange", () => { this.current = window.location.hash.slice(1) || "/"; }) } } VueRouter.install = (_Vue) => { Vue = _Vue; Vue.mixin({ beforeCreate() { if (this.$options.router) { Vue.prototype.$router = this.$options.router; } } }); Vue.component("router-link", { props: { to: { type: String, required: true, } }, render(h) { return h("a", { attrs: { href: `#${this.to}` }, }, this.$slots.default ); } }); Vue.component("router-view", { render(h) { let component = null; const current = this.$router.current; const route = this.$router.$options.routes.find( (route) => route.path === current ) if (route) component = route.component; return h(component); } }) } export default VueRouter;
Version with personal comments:
// 1. Implement a plug-in // 2. Two components // How to write Vue plug-in // A plug-in is either a function or an object // The plug-in must implement an install method, which will be called by vue in the future let Vue; // Save the constructor of Vue and use it in the plug-in class VueRouter { constructor(options) { this.$options = options; // Only after the current is changed into responsive data can you modify and re execute the render function in router view let initial = window.location.hash.slice(1) || "/"; Vue.util.defineReactive(this, "current", initial); window.addEventListener("hashchange", () => { // Get # what's next this.current = window.location.hash.slice(1) || "/"; }) } } VueRouter.install = (_Vue) => { Vue = _Vue; // 1. Mount the $router attribute (the VueRouter instance object from new in router/index.js cannot be obtained), // Because Vue.use needs to point faster, it can only be used by introducing router in main.js // this.$router.push() // Global mixing (delay the following logic until the router is created and attached to the option) Vue.mixin({ beforeCreate() { // Note that this hook is called when each component creates an instance // Determine whether the root instance has this option if (this.$options.router) { /** * Because each Vue component instance inherits the methods above Vue.prototype, you can * In each component, you can access the new VueRouter instance initialized in router/index.js through this.$router */ Vue.prototype.$router = this.$options.router; } } }); // Implement two components: router link and router view // < router link to = "/" > hone < / router link > so we need to convert this router link label to: < a href = "/" > home</a> /** * The second parameter is actually a template, that is, a rendering component dom * What we use here is the rendering function, which returns a virtual DOM */ Vue.component("router-link", { props: { to: { type: String, required: true, } }, render(h) { return h("a", { attrs: { // In order not to update the page again, the anchor point is used here href: `#${this.to}` }, }, // If you want to get Home, you can do the following this.$slots.default ); } }); Vue.component("router-view", { render(h) { let component = null; // Because this.$router is obtained by mixing, you can get the component corresponding to the current route and render it const current = this.$router.current; const route = this.$router.$options.routes.find( (route) => route.path === current ) if (route) component = route.component; return h(component); } }) } export default VueRouter;
Step by step analysis - start from scratch
First, there are several questions
Question 1:
In router/index.js
import Router from 'vue-router' Vue.use(Router)
As we know, Vue.use() introduces a plug-in
So what does the plug-in Vue router do internally?
Question 2:
In router/index.js
import Router from 'vue-router' export default new Router({ routes: [ { path: '/', name: 'Home', component: Home }, { path: '/about', name: 'About', component: About } ] })
- An imported Vue router plug-in object is initialized
- What is passed in brackets is a {} object, which is actually a configuration item
- The configuration item contains a routes routing table
Then in main.js
import Vue from 'vue' import App from './App' import router from './router' Vue.config.productionTip = false new Vue({ el: '#app', router, components: { App }, template: '<App/>' })
When creating a new Vue instance, the exported router is imported as a configuration item. Why?
Question 3: router link and router view
- The router link component is used in the component to realize route jump
- Use the router view component as the exit of the route
So, how are these two components implemented internally?
Why? Other components can only be used after they are declared in the Component, but if these two components are used directly, it means that these two components must have been globally registered somewhere
General idea of expansion:
In fact, it is implemented in jquery in this way: by listening to the transformation of the current hash value or the change of history, you can get a triggered event, and then you can get the current address (that is, the address to jump) Then, through this address, you can go to the routing table defined in our router/index.js, that is, match the path to get the component, so that you can get the component, and then get the real DOM, and then add it to our router view, that is, empty the contents of the previous router view, and then press the latest DOM into the router view As shown, this is a typical DOM operation
But there is a new thing in Vue: Vue's responsive principle, so you can use responsive to monitor routing changes
What is Vue's plug-in
Learn from: Deeply understand Vue's plug-in mechanism and install detailed _vue.js_script home (jb51.net)
- Why does the plug-in implement an install method
Vue plug-ins should expose an install method. The e first parameter of this method is the Vue constructor, and the second parameter is an optional option object -- this is the Vue official specification for Vue plug-ins,
What can the install function do?
How is install implemented internally?
What exactly does the plug-in do in install?
Classic triple question~
Handling of install in plug-ins such as Vue router
Throw a question:
- Why can you use $router directly in a project to get the value and some methods
- Why do these plug-ins have to be introduced with Vue.use before creating an instance, and then introduced in Vue instances
Example of using Vue router
class Router { constructor(options) { } } Router.install = function(_Vue) { _Vue.mixin({ beforeCreate() { if (this.$options.router) { _Vue.prototype.$router = this.$options.router } } }) } export default Router;
- _What is Vue.mixin global mixing? It is equivalent to mixing this method in all components;
- What is beforeCreate? Of course, it is a life cycle of Vue, which is executed before create;
So:
-
Vue router uses a global blend in the install function, and puts this when the beforeCreate life cycle is triggered o p t i o n . r o u t e r hang load reach V u e of primary type upper Yes , that Do you this kind Just can with send use t h i s . option.router is mounted on the Vue prototype, so you can use this option.router is mounted on the Vue prototype, so you can use this.router to call the router instance
-
So what is this.$options.router
- this. In the global mix o p t i o n s yes I Guys stay stay m a i n . j s in n e w V u e ( ) of Time Wait large Include number in noodles pass enter of match Set term , place with I Guys m a i n . j s pass enter of r o u t e r , stay this in Just can with through too t h i s . options is the configuration item passed in the {} braces when we new Vue ({}) in main.js, so the router passed in from main.js can be passed through this Options is the configuration item we passed in braces when we newVue() in main.js, so the router we passed in from main.js can get our new Vue router instance in router/index.js through this.options.router
Why is it designed like this: because in router/index.js
import Vue from 'vue' import Router from 'vue-router' import Home from '@/components/home' import About from '@/components/about' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'Home', component: Home }, { path: '/about', name: 'About', component: About } ] })
The Vue.use is executed first and then the new Vue router object is operated. Therefore, if you want to use this Vue router instance in the plug-in install, you need to pass the instance into the new Vue ({}) configuration item of main.js. In this way, we can obtain the routing table defined in the new Router ({}) by dependency injection,
We put Vue.prototype r o u t e r = t h i s . router = this. router=this.options.router; so other components can use this r o u t e r Obtain take interview ask reach I Guys set righteousness of road from surface Yes , place with by What Do you can with use t h i s . The router has accessed the routing table defined by us, so why can we use this The router has accessed the routing table defined by us, so why can we use this.router.push() to add routes? Part of the reason is that this.$router routing table is an array, so it can be pushed
- Vue.use mainly calls the install method inside the plug-in and passes in the Vue instance as a parameter
Internal implementation of plug-in install in vue
The following is the source code of Vue.use
export function initUse (Vue: GlobalAPI) { // Register a use method mounted on the instance Vue.use = function (plugin: Function | Object) { // Initializes the array of the current plug-in const installedPlugins = (this._installedPlugins || (this._installedPlugins = [])) // If the plug-in has been registered, it will not be processed if (installedPlugins.indexOf(plugin) > -1) { return this } ... // Here comes the point!!! if (typeof plugin.install === 'function') { // When install is a function in the plug-in, call the install method, point to the plug-in, and pass in a number of parameters plugin.install.apply(plugin, args) } else if (typeof plugin === 'function') { // When the plug-in itself is a function, take it as an install method, point to the plug-in, and pass in a number of parameters plugin.apply(null, args) } // Put the plug-in into the plug-in array installedPlugins.push(plugin) return this } }
See here, everyone should have a certain understanding of the plug-in, stick to it!!
Start implementation
- First: because router/index initializes the instance of the plug-in, the plug-in can be represented by a class, and an install method should be implemented
class VueRouter { } VueRouter.install = (_Vue) => { }
As mentioned above, the first parameter of the plug-in install method is the Vue instance itself
optimization
vue instances are also used in other places later, so we declare a global vue in the plug-in to save the incoming vue instance
And: it is also a way to ensure the independence of the plug-in and vue. After this operation, when we package the plug-in, we will not package vue into the plug-in
And mount the configuration item router from new Vue ({router}) to the Vue instance prototype object
let Vue; class VueRouter { } VueRouter.install = (_Vue) => { Vue = _Vue; Vue.mixin({ beforeCreate() { if (this.$options.router) { Vue.prototype.$router = this.$options.router; } } }) }
Not only that, we also implemented two components, router link and router view, in the install function
Principle:
Home, so we need to convert this router link tag to: Home
- Receive a to attribute
- And it returns a render rendering function, that is, it returns a virtual DOM
So how to get the text Home in the middle of router link?
Expansion: Vue.$slots
Therefore, because there is only home text in router link, you can get it directly through vue.$slots.default
let Vue; class VueRouter { } VueRouter.install = (_Vue) => { Vue = _Vue; Vue.mixin({ beforeCreate() { if (this.$options.router) { Vue.prototype.$router = this.$options.router; } } }); Vue.component("router-link", { props: { to: { type: String, required: true, } }, render(h) { return h("a", { attrs: { // In order not to update the page again, the anchor point is used here href: `#${this.to}` }, }, // If you want to get Home, you can do the following this.$slots.default ); } }); }
The above is the specific implementation of router link
The following is the router view implementation
Principle: get the current route, find the corresponding component from the route table and render it
Note: in the install method, we mount the vue router instance instantiated in router/index.js to $router on the vue prototype object through global mixing
- Then: we can get our instantiated component through this.$router in the component
Here's how to implement the class of the plug-in
In router/index.js, we
new Router({ routes: [ { path: '/', name: 'Home', component: Home }, { path: '/about', name: 'About', component: About } ] })
A routing table is passed in as the configuration item of this plug-in instance
So we can get this configuration item through parameters in the constructor of this class. In order to get the routing table in other components, we mount the configuration item to the class itself
class VueRouter { constructor(options) { this.$options = options } }
Why?
In this case, in these components of router view
You can use this r o u t e r . router. router.options access the routing table in the configuration item passed in the Vue router class of new in router/index
class VueRouter { constructor(options) { this.$options = options this.current = window.location.hash.slice(1) || "/"; window.addEventListener("hashchange", () => { // Get # what's next this.current = window.location.hash.slice(1) || "/"; }) } }
Initialize current, listen for route changes through onhashchange, and assign values to current
slice (1) is used to get the # following value
In this way, the router view component can be implemented
let Vue; class VueRouter { constructor(options) { this.$options = options this.current = window.location.hash.slice(1) || "/"; window.addEventListener("hashchange", () => { // Get # what's next this.current = window.location.hash.slice(1) || "/"; }) } } VueRouter.install = (_Vue) => { Vue = _Vue; Vue.mixin({ beforeCreate() { if (this.$options.router) { Vue.prototype.$router = this.$options.router; } } }); Vue.component("router-link", { props: { to: { type: String, required: true, } }, render(h) { return h("a", { attrs: { // In order not to update the page again, the anchor point is used here href: `#${this.to}` }, }, // If you want to get Home, you can do the following this.$slots.default ); } }); Vue.component("router-view", { render(h) { let component = null; // Because this.$router is obtained by mixing, you can get the component corresponding to the current route and render it const current = this.$router.current; const route = this.$router.$options.routes.find( (route) => route.path === current ) if (route) component = route.component; return h(component); } }) }
So the current code is like this
However, we can find that the current has changed and the router view remains unchanged. This is because the current is not a responsive data at this time. Therefore, when the current changes, the render function in the router view will not execute and render again
Therefore, the current in the class class will become responsive data
Extension: Vue.util.defineReactive
Vue.util.defineReactive(obj,key,value,fn)
obj: target object,
key: target object attribute;
Value: attribute value
fn: called only when set is in the node debugging environment
In fact, the bottom layer is an Object.defineProperty()
Dependencies are collected through dep, and the ob attribute is added through the Observer class
class VueRouter { constructor(options) { this.$options = options; // Only after the current is changed into responsive data can you modify and re execute the render function in router view let initial = window.location.hash.slice(1) || "/"; Vue.util.defineReactive(this, "current", initial); window.addEventListener("hashchange", () => { // Get # what's next this.current = window.location.hash.slice(1) || "/"; }) } }
So the complete code is:
// 1. Implement a plug-in // 2. Two components // How to write Vue plug-in // A plug-in is either a function or an object // The plug-in must implement an install method, which will be called by vue in the future let Vue; // Save the constructor of Vue and use it in the plug-in class VueRouter { constructor(options) { this.$options = options; // Only after the current is changed into responsive data can you modify and re execute the render function in router view let initial = window.location.hash.slice(1) || "/"; Vue.util.defineReactive(this, "current", initial); window.addEventListener("hashchange", () => { // Get # what's next this.current = window.location.hash.slice(1) || "/"; }) } } VueRouter.install = (_Vue) => { Vue = _Vue; // 1. Mount the $router attribute (the VueRouter instance object from new in router/index.js cannot be obtained), // Because Vue.use needs to point faster, it can only be used by introducing router in main.js // this.$router.push() // Global mixing (delay the following logic until the router is created and attached to the option) Vue.mixin({ beforeCreate() { // Note that this hook is called when each component creates an instance // Determine whether the root instance has this option if (this.$options.router) { /** * Because each Vue component instance inherits the methods above Vue.prototype, you can * In each component, you can access the new VueRouter instance initialized in router/index.js through this.$router */ Vue.prototype.$router = this.$options.router; } } }); // Implement two components: router link and router view // < router link to = "/" > hone < / router link > so we need to convert this router link label to: < a href = "/" > home</a> /** * The second parameter is actually a template, that is, a rendering component dom * What we use here is the rendering function, which returns a virtual DOM */ Vue.component("router-link", { props: { to: { type: String, required: true, } }, render(h) { return h("a", { attrs: { // In order not to update the page again, the anchor point is used here href: `#${this.to}` }, }, // If you want to get Home, you can do the following this.$slots.default ); } }); Vue.component("router-view", { render(h) { let component = null; // Because this.$router is obtained by mixing, you can get the component corresponding to the current route and render it const current = this.$router.current; const route = this.$router.$options.routes.find( (route) => route.path === current ) if (route) component = route.component; return h(component); } }) } export default VueRouter;
For later optimizations, such as changing the mode (history and hash) through mode, hash is used by default, and routing interceptors.