preface
As an important member of the VUE ecosystem: VuE-Router has become one of the basic skills for front-end engineers to master. This article put aside the vue-Router daily use, start from the source code, learn the source code together and try to achieve a vue-Router of their own. In the process of reading, if there is any inadequacy, please correct.
It takes about 15 minutes to read this article, which has over 2000 words. If there are any deficiencies, please correct them
Write a vuex by hand from 0 to 1
Source analyses
Let’s take a look at the source directory structure:
/ / path: node_modules/vue - the router ├ ─ ─ dist -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- build output directory file after ├ ─ ─ package. The json -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - don't explain ├ ─ ─ the SRC -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - contains the main source │ ├ ─ ─ the components -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- routing component │ │ ├ ─ ─ the link. The js -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the router link component │ │ ├ ─ ─ the js, the router - the view component │ ├ ─ ─history-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - routing patterns │ │ ├ ─ ─ the abstract. The js -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the abstract routing patterns │ │ ├ ─ ─ base. Js -----------------------historyRouting patterns │ │ ├ ─ ─ errors. Js -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- wrong class to handle │ │ ├ ─ ─ hash. Js -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --hashRouting patterns │ │ ├ ─ ─ HTML 5. Js -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- HTML5History patterns encapsulate │ ├ ─ ─ util -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- tools function encapsulation │ ├ ─ ─ The create - the matcher. Js -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the routing map │ ├ ─ ─ the create - the route - map. Js -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - to create routing tree │ mapping state ├ ─ ─ index. Js -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- main entry file | ├ ─ ─ the js -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the routing load fileCopy the code
Entry start analysis
Vue instance – the router
From the entry file index.js, we can see that a VueRouter class is exposed. This is the new Router() we used when we introduced vue-Router in the vue project.
exportdefault class VueRouter { constructor (options: RouterOptions = {}) { this.app = null this.apps = [] this.options = options this.beforeHooks = [] this.resolveHooks = [] this.afterHooks = [] this.matcher = createMatcher(options.routes || [], this)let mode = options.mode || 'hash'
this.fallback = mode === 'history'&&! supportsPushState && options.fallback ! = =false
if (this.fallback) {
mode = 'hash'
}
if (!inBrowser) {
mode = 'abstract'
}
this.mode = mode
switch (mode) {
case 'history':
this.history = new HTML5History(this, options.base)
break
case 'hash':
this.history = new HashHistory(this, options.base, this.fallback)
break
case 'abstract':
this.history = new AbstractHistory(this, options.base)
break
default:
if(process.env.NODE_ENV ! = ='production') {
assert(false, `invalid mode: ${mode}`)}}}init () {}
function registerHook() {}go() {}push(){}
VueRouter.install = install
VueRouter.version = '__VERSION__'
if (inBrowser && window.Vue) {
window.Vue.use(VueRouter)
}
Copy the code
From the entry file, we can see that it contains the following main steps:
- Example Initialize the routing mode
- According to incoming
routes
Parameter generates a route status table - Gets the current route object
- Initialize the routing function
- registered
Hooks
Events such as - add
install
Load function
Install registration function
The Router class exposed above has an install method mounted on it, which is briefly analyzed here (this is also a mental guide to implementing a route of your own). When we introduced the vue-Router and instantiated it, the vue-Router internals helped us load the Router instance into the vue instance so that we could use router-link, router-View, and other components directly in the component. This.$router, this.$route and other global variables.
- First introduced
vue-router
Post need utilizationbeforeCreate
The life cycle loads it for initialization_routerRoot, _router, _route
Such as data, - Also set global access variables
$router
and$router
- complete
router-link
androuter-view
Registration of two components
This can be seen in the source install.js
import View from './components/view'
import Link from './components/link'
export let _Vue
export function install (Vue) {
if (install.installed && _Vue === Vue) return
install.installed = true_Vue = Vue // mixin Vue. Mixin ({beforeCreate () {
if (isDef(this.$options.router)) {
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
}
registerInstance(this, this)
},
destroyed() {registerInstance(this)}}) // Set global access variables$router
Object.defineProperty(Vue.prototype, '$router', {
get () { return this._routerRoot._router }
})
Object.defineProperty(Vue.prototype, '$route', {
get () { returnThis._routerroot. _route}}) Vue.component('RouterView', View)
Vue.component('RouterLink', Link)
}
Copy the code
Above we on vue-Router source code to do a rough context comb, we will implement a simplified version of vue-Router. Before that, we need to briefly understand some knowledge points
Front knowledge
Vue-router provides a mode parameter, you can set the history parameter or hash parameter, and the implementation principle is different
Implementation principle of Hash
http://localhost:8080/#login
The # symbol itself and the character following it are called hashes and can be read using the window.location.hash property. H5 adds a hashchange to help us listen for hash changes in browser links.
<! DOCTYPE html> <html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge"</title> </head> <body> <h1 id="app"></h1>
<a href="#/jin"< p style = "max-width: 100%; clear: both; min-height: 1em"#/sn"</a> <script> window.addeventListener ('hashchange',()=>{
app.innerHTML = location.hash.slice(2)
})
</script>
</body>
</html>
Copy the code
How history works
http://localhost:8080/login also H5 added pushState and popstate no awareness to help us refresh the browser url
<body>
<h1 id="app"></h1>
<a onclick="to('jin')"< span style = "max-width: 100%; clear: both; min-height: 1em"to('sn')"</a> <script >function to(pathname) {
history.pushState({},null,pathname)
app.innerHTML = pathname
}
window.addEventListener('popstate',()=>{
to(location.pathname)
})
</script>
</body>
Copy the code
After we understand the principles of non-perceptive refresh URLS, we will encapsulate a VUe-Router based on these principles
Start implementing your own VUe-Router
Implement the install load method
First we need to initialize our project structure and create a new simple-vue-router.js. Based on the above analysis, we need to expose a Router class that contains an install method
letVue // Saves the Vue instance classVueRouter(){// Router class}functionInstall (_vue){// Install function}export default {
VueRouter,
install
}
Copy the code
Install needs to do the following
- Initialize the
_routerRoot
._router
._route
Such as data, - Set global access variables
$router
and$router
- complete
router-link
androuter-view
Registration of two components
The code is as follows:
letVue // Used to save Vue instance class VueRouter {// Router class} VueRouter. Install =function(_vue) {// Load functions // Each component has this.$router / this.$routeSo mixin Vue = _vue // This is available in every component.$routerWith this.$routeVue. Mixin ({beforeCreate() {// If it is the root componentif (this.$options && this.$options.router) {this._root = this // Save the current vue instance to _root.$options.router // Mount router instance on _router}else{// If it is a child it inherits the parent (so that all components share a router instance) this._root = this.$parent._root} // Define the router instance when accessing this.$routerRouter instance Object.defineProperty(this,'$router', {
get() {
returnThis._root. _router}}) // Define route when accessing this.$routeObject.defineproperty (this,'$route', {
get() {
returnVue.component(vue.component (vue.component (vue.component))'router-link', {
render(h) {}
})
Vue.component('router-view', {
render(h) {}
})
}
export default VueRouter
Copy the code
implementationrouter
class
Now that the install method has been implemented to help us mount the Router in the Vue instance, we need to improve the functionality in the Router class. According to the above source code analysis, we need to achieve the following functions:
- Generate a routing status table based on the passed Rotues parameters. That is, if the passed argument is
routes:[
{
path: '/',
name: 'index',
component: index
},
{
path: '/login',
name: 'login',
component: login
},
{
path: '/learn',
name: 'learn',
component: learn
},
]
Copy the code
Format it with path as key and Component as value
{
'/':index,
'/login':login,
'/learn':learn
}
Copy the code
- Define the current route variable and render the corresponding component in real time by hijacking
- Define a function that implements the processing that should be used for different schema responses
The specific code is as follows
letVue // Save Vue instance class VueRouter {// Router class constructor(options) {// DefaulthashMode. This mode = options. Mode | |'hash'This. Routes = options. Routes | | [] / / routing map enclosing routeMaps = this. GenerateMap (enclosing routes) / / the current routing enclosing currentHistory = newhistoryRoute() // Initialize Route function this.initroute ()} generateMap(routes) {return routes.reduce((prev, current) => {
prev[current.path] = current.component
return prev
}, {})
}
initRoute() {// only processing herehashPatterns andhistorymodelif (this.mode === 'hash') {// Check if the user opens it in the URLhashThere is no redirect to# /
location.hash ? ' ' : (location.hash = '/'// Monitor browser load events to change the currently stored routing variable window.adDeventListener ('load', () => {
this.currentHistory.current = location.hash.slice(1)
})
window.addEventListener('hashchange', () => {
this.currentHistory.current = location.hash.slice(1)
})
} else {
location.pathname ? ' ' : (location.pathname = '/')
window.addEventListener('load', () => {
this.currentHistory.current = location.pathname
})
window.addEventListener('popstate', () => {
this.currentHistory.current = location.pathname
})
}
}
}
class historyRoute {
constructor() {
this.current = null
}
}
VueRouter.install = function(_vue) {// omit some code}export default VueRouter
Copy the code
Improve the code to achieve real-time refresh page view
After building the Router class, we found that the current route state currentHistory. Current is static, and the page does not display the template when we change the current route. Here we can use vue’s own bidirectional binding mechanism
The specific code is as follows
letVue // Save Vue instance class VueRouter {// Router class constructor(options) {// DefaulthashMode. This mode = options. Mode | |'hash'This. Routes = options. Routes | | [] / / routing map enclosing routeMaps = this. GenerateMap (enclosing routes) / / the current routing enclosing currentHistory = newhistoryRoute() // Initialize Route function this.initroute ()} generateMap(routes) {return routes.reduce((prev, current) => {
prev[current.path] = current.component
return prev
}, {})
}
initRoute() {// only processing herehashPatterns andhistorymodelif (this.mode === 'hash') {// Check if the user opens it in the URLhashThere is no redirect to# /
location.hash ? ' ' : (location.hash = '/'// Monitor browser load events to change the currently stored routing variable window.adDeventListener ('load', () => {
this.currentHistory.current = location.hash.slice(1)
})
window.addEventListener('hashchange', () => {
this.currentHistory.current = location.hash.slice(1)
})
} else {
location.pathname ? ' ' : (location.pathname = '/')
window.addEventListener('load', () => {
this.currentHistory.current = location.pathname
})
window.addEventListener('popstate', () => {
this.currentHistory.current = location.pathname
})
}
}
}
class historyRoute {
constructor() {
this.current = null
}
}
VueRouter.install = function(_vue) {// Load functions // Each component has this.$router / this.$routeSo mixin Vue = _vue // This is available in every component.$routerWith this.$routeVue. Mixin ({beforeCreate() {// If it is the root componentif (this.$options && this.$options.router) {this._root = this // Save the current vue instance to _root.$optionsRouter // Use vue library to hijack the current route vue.util. DefineReactive (this,'route',this._router.currentHistory)
} else{// If it is a child it inherits the parent (so that all components share a router instance) this._root = this.$parent._root} // Define the router instance when accessing this.$routerRouter instance Object.defineProperty(this,'$router', {
get() {
returnThis._root. _router}}) // Define route when accessing this.$routeObject.defineproperty (this,'$route', {
get() {
return{// Current route: this._root._router.history.current}}})}}) Vue.component('router-link', { props: { to: String, tag: String }, methods: {handleClick(event) {// prevent a tag from skipping event && event.preventDefault && event.preventdefault ()let mode = this._self._root._router.mode
let path = this.to
this._self._root._router.currentHistory.current = path
if (mode === 'hash') {
window.history.pushState({}, ' '.'# /' + path.slice(1))
} else {
window.history.pushState({}, ' ', path.slice(1))
}
}
},
render(h) {
let mode = this._self._root._router.mode
let tag = this.tag || 'a'
return (
<tag on-click={this.handleClick} href={mode === 'hash' ? `#${this.to}` : this.to}>
{this.$slots.default}
</tag>
)
}
})
Vue.component('router-view', {render(h) {// this current is already dynamic through the above hijackinglet current = this._self._root._router.currentHistory.current
let routeMap = this._self._root._router.routeMaps
returnH (routeMap[current]) // Dynamic render corresponding component}})}export default VueRouter
Copy the code
At this point, a simple route manager is complete. In fact, compared to vue-Router, it still lacks many functions such as navigation guard and dynamic routing. A journey of a thousand miles begins with a single step. This article aims to briefly analyze the vue-Router principle and practice a simple router to help you enter the door of vue-Router principle. The following will rely on your own perseverance to continue in-depth learning.
The resources
Vue-router official source handwritten vue-router source code