Vue – principle of the Router
Hash pattern
- The content after # in the URL is the path address
- Listen for the Hashchange event
- Find the corresponding component based on the current routing address and re-render it
The History mode
- Change the address bar with the history.pushstate () method
- Listen for popState events
- Find the corresponding component based on the current routing address and re-render it
Write a Router
The following methods are all disassembly methods and will eventually be reassembled
Analysis of the
Review the core code
Vue.use(VueRouter) Use when an object is passed in internally, the install method of the object is called
// Create a routing object
const router = new VueRouter({
routes: [{name: 'home'.path: '/'.component: homeComponent }
]
})
// Create a Vue instance and register the Router object
new Vue({
router,
render: h= > h(App)
}).$mount('#app')
Copy the code
- Vue.use calls the install method of the object when it is passed in internally, so let’s deal with the install method first
The install method
- Install accepts two arguments
- Constructor of vue
- Optional option object (we don’t use it here, so we don’t pass it)
- The method is divided into three steps
- If the plug-in is already installed, return directly
- Record the constructor of vue in the global variable, because we will use the constructor of vue in VueRouter’s instance method in the future
- Inject the Router object passed in when creating the Vue instance
let _Vue = null
export default class VueRouter {
// Pass in two arguments, one to the vue constructor and the second to the optional option object
static install (Vue) {
// 1. Return if the plug-in is already installed
if (VueRouter.install.installed && _vue === true) return
VueRouter.install.installed = true // Indicates that the plug-in is installed
// 2. Record the vue constructor in the global variable, since we will use the vue constructor in VueRouter's instance method in the future
_Vue = Vue
// 3. Inject the router object passed into the Vue instance
Vue.mixin({
beforeCreate () {
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
}
}
})
}
}
Copy the code
constructor
Again, we don’t need to do much, just declare three properties
- this.options = options
Record the option passed in the constructor, options
- this.routeMap = {}
Routes = routes; routes = routes; routes = routes
- this.data = _Vue.observable({ current: ‘/’ })}
Observable creates a reactive object that stores the current route address (/ by default)
constructor (options) {
// Record the options passed in the constructor
this.options = options
// Add routes passed in options. // Add routes
this.routeMap = {}
// Data is a responsive object, because data stores the current route address and automatically loads components when the route changes
this.data = _Vue.observable({ // Observable creates a responsive object
current: '/' // Stores the current routing address. The default is /})}Copy the code
createRouteMap
Convert routes (routing rules) from the option passed in the constructor to key-value pairs into the key-routing address, value-routing component of the routeMap object
createRouteMap () {
// All routing rules are iterated over and stored as key-value pairs in a routeMap object
this.options.routes.forEach(route= > {
this.routeMap[route.path] = route.component
})
}
Copy the code
initComponents
This method is used to create router-link and router-view
router-link
initComponents (Vue) {
Vue.component('router-link', {
props: {
to: String
},
template: '<a :href="to"><slot></slot></a>'})}Copy the code
Pay attention to the point
-
If we use vue_CLI to create the project and run it, we will get some errors using the above creation method because we used template to create the tag
-
The runtime version of vue used by vue_CLI does not support the Template template and needs to be compiled in advance when packaged
There are two solutions
-
Modify the vue. Config. js configuration to use the full vue
It contains both the runtime and the compiler, and is about 10K larger than the runtime version. When the program runs, the template is converted into the render function
module.exports = { // Render the full vue runtimeCompiler: true } Copy the code
-
Using the render function
The runtime version of Vue does not come with a compiler, so it does not support the template option in components. The compiler compiles the template into the render function, and the runtime component can write the render function directly
The single-file component used the render function without template because the single-file template was compiled into the render function during the packaging process, which is called precompilation
render
-
The render function takes an argument, usually called h, to help us create the virtual DOM, and H is passed by vue
-
The h function takes three arguments
- Create a selector for this element.
- Set some attributes for the tag, and if they are DOM object attributes, add them to Attrs.
- The child element of the generated element, so it is in array form
initComponents (Vue) { Vue.component('router-link', { props: { to: String }, /* The runtime version of vue does not come with a compiler, so it does not support the template option in components. The compiler compiles template into the render function. The single-file component always uses the render template because the single-file template was compiled into the render function during the packaging process, which is called precompiling */ render (h) { // This function takes an argument, usually called h, to help us create the virtual DOM. H is passed by vue return h('a', { The h function takes three arguments: 1. Create a selector for this element. 2. Set some attributes for the tag. If they are DOM object attributes, add them to attrs. 3. A child element of the generated element, so it is an array attrs: { href: this.to } }, [this.$slots.default]) } // template: '<a :href="to"><slot></slot></a>'})}Copy the code
router-view
const self = this
Vue.component('router-view', {
// Obtain the routing component corresponding to the current routing address
render (h) {
const component = self.routeMap[self.data.current]
return h(component)
}
})
Copy the code
Check for problems by combining the above methods
- Because we only call the install method by default, but we have a few other methods to call, and we need one moreinitMethod, called after the install method has been called
CreateRouteMap with initComponents () ()
init () {
this.createRouteMap()
this.initComponents(_Vue)
}
Copy the code
-
Combine all of the above methods (remove comments for ease of reading)
/ /.. /vuerouter/index.js let _Vue = null export default class VueRouter { static install (Vue) { if (VueRouter.install.installed && _Vue === true) return VueRouter.install.installed = true _Vue = Vue Vue.mixin({ beforeCreate () { if (this.$options.router) { _Vue.prototype.$router = this.$options.router this.$options.router.init() } } }) } constructor (options) { this.options = options this.routeMap = {} this.data = _Vue.observable({ current: '/' }) } init () { this.createRouteMap() this.initComponents(_Vue) this.initEvent() } createRouteMap () { this.options.routes.forEach(route= > { this.routeMap[route.path] = route.component }) } initComponents (Vue) { Vue.component('router-link', { props: { to: String }, render (h) { return h('a', { attrs: { href: this.to } }, [this.$slots.default]) } }) const self = this Vue.component('router-view', { render (h) { const component = self.routeMap[self.data.current] return h(component) } }) } } // Remember to replace VueRouter introduced in router/index.js import Vue from 'vue' // import VueRouter from 'vue-router' import VueRouter from '.. /vuerouter' import Home from '.. /views/Home.vue' import Home from '.. /views/About.vue' Vue.use(VueRouter) const routes = [ { path: '/'.name: 'Home'.component: Home }, { path: '/about'.name: 'About'.component: About } ] const router = new VueRouter({ routes }) export default router Copy the code
- After the actual test, we found that there were still some minor issues, namely when we clicked router-link, we changed the path to refresh the page, but in the single-page component, we didn’t need to refresh the page, so we needed to make some minor adjustments to router-Link
To perfect the router – the link
-
We need to add a method to the A tag that does three things
- PushState changes the browser address bar without sending a request to the server
- Synchronize the current routing path to the default value
- Prevents label default events
The pushState method takes three arguments
- The data.
- Title Specifies the title of the page.
- The url address.
Vue.component('router-link', { props: { to: String }, render (h) { return h('a', { attrs: { href: this.to }, on: { click: this.clickHandler } }, [this.$slots.default]) }, methods: { clickHandler (e) { // 1. Use pushState to change the browser address bar without sending a request to the server history.pushState({}, ' '.this.to) // This method takes three arguments, 1.data. 2. Title Specifies the title of the page. 3. The url address this.$router.data.current = this.to e.preventDefault() } } }) Copy the code
Final refinement
After perfecting the above router-link, we implemented vue-Router, but we still need to add a small method to make the final improvement, namely the forward and backward function in the upper left corner of the browser
- This is done by listening to popState to assign the current routing path to current ** (remember to add it to the init method) **
initEvent () {
window.addEventListener('popstate'.() = > {
this.data.current = window.location.pathname
})
}
Copy the code
- Final complete version of the code (in two versions, annotated at the end)
let _Vue = null
export default class VueRouter {
static install (Vue) {
if (VueRouter.install.installed && _Vue === true) return
VueRouter.install.installed = true
_Vue = Vue
Vue.mixin({
beforeCreate () {
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
this.$options.router.init()
}
}
})
}
constructor (options) {
this.options = options
this.routeMap = {}
this.data = _Vue.observable({
current: '/'
})
}
init () {
this.createRouteMap()
this.initComponents(_Vue)
this.initEvent()
}
createRouteMap () {
this.options.routes.forEach(route= > {
this.routeMap[route.path] = route.component
})
}
initComponents (Vue) {
Vue.component('router-link', {
props: {
to: String
},
render (h) {
return h('a', {
attrs: {
href: this.to
},
on: {
click: this.clickHandler
}
}, [this.$slots.default])
},
methods: {
clickHandler (e) {
history.pushState({}, ' '.this.to)
this.$router.data.current = this.to
e.preventDefault()
}
}
})
const self = this
Vue.component('router-view', {
render (h) {
const component = self.routeMap[self.data.current]
return h(component)
}
})
}
initEvent () {
window.addEventListener('popstate'.() = > {
this.data.current = window.location.pathname
})
}
}
Copy the code
let _Vue = null
export default class VueRouter {
// Pass in two arguments, one to the vue constructor and the second to the optional option object
static install (Vue) {
// 1. Return if the plug-in is already installed
if (VueRouter.install.installed && _Vue === true) return
VueRouter.install.installed = true // Indicates that the plug-in is installed
// 2. Record the vue constructor in the global variable, since we will use the vue constructor in VueRouter's instance method in the future
_Vue = Vue
// 3. Inject the router object passed into the Vue instance
Vue.mixin({
beforeCreate () {
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
this.$options.router.init()
}
}
})
}
constructor (options) {
// Record the options passed in the constructor
this.options = options
// Add routes passed in options. // Add routes
this.routeMap = {}
// Data is a responsive object, because data stores the current route address and automatically loads components when the route changes
this.data = _Vue.observable({ // Observable creates a responsive object
current: '/' // Stores the current routing address. The default is /
})
}
init () {
this.createRouteMap()
this.initComponents(_Vue)
this.initEvent()
}
// Convert routes from the option passed in the constructor to key-value pairs in the routeMap object key-routing address, value-routing component
createRouteMap () {
// All routing rules are iterated over and stored as key-value pairs in a routeMap object
this.options.routes.forEach(route= > {
this.routeMap[route.path] = route.component
})
}
// Create two components, router-link and router-view
initComponents (Vue) { // Accept parameters to reduce contact with the outside world
Vue.component('router-link', {
props: {
to: String
},
/* The runtime version of vue does not come with a compiler, so it does not support the template option in components. The compiler compiles template into the render function. The single-file component always uses the render template because the single-file template was compiled into the render function during the packaging process, which is called precompiling */
render (h) { // This function takes an argument, usually called h, to help us create the virtual DOM. H is passed by vue
return h('a', { The h function takes three arguments: 1. Create a selector for this element. 2. Set some attributes for the tag. If they are DOM object attributes, add them to attrs. 3. A child element of the generated element, so it is an array
attrs: {
href: this.to
},
on: {
click: this.clickHandler
}
}, [this.$slots.default])
},
methods: {
clickHandler (e) {
// 1. Use pushState to change the browser address bar without sending a request to the server
history.pushState({}, ' '.this.to) // This method takes three arguments, 1.data. 2. Title Specifies the title of the page. 3. The url address
this.$router.data.current = this.to
e.preventDefault()
}
}
// template: '<a :href="to"><slot></slot></a>'
})
const self = this
Vue.component('router-view', {
// Obtain the routing component corresponding to the current routing address
render (h) {
const component = self.routeMap[self.data.current]
return h(component)
}
})
}
initEvent () {
window.addEventListener('popstate'.() = > {
this.data.current = window.location.pathname
})
}
}
Copy the code