Vue – Router principle analysis
The overall content is long and requires a certain amount of patience. You can first understand the general idea and then make breakthroughs one content at a time. Let’s start with the basic use of vue-Router.
1.1 Defining a routing file
// router.js
import Router from 'vue-router'
Vue.use(Router)
const routes = [
{
path: '/'.name: 'Home'.component: Home
},
{
path: '/about'.name: 'About'.component: () = > import(/* webpackChunkName: "about" */ '.. /views/About.vue'),
children:[
{
path: 'a'.name: 'a'.component: () = >import(/* webpackChunkName: "aboutA" */ '.. /views/AboutA.vue')}, {path: 'b'.name: 'b'.component: () = >import(/* webpackChunkName: "aboutA" */ '.. /views/AboutB.vue'}]}]const router = new VueRouter({
routes
})
router.beforeEach((to, from, next) = > {
console.log('beforeEach')
next()
})
export default router
Copy the code
1.2 Introduced in main.js
// main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
router,
name: 'main'.render: h= > h(App)
}).$mount('#app')
Copy the code
1.3 Adding router-View and router-link components to app.vue
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> |
</div>
<router-view/>
</div>
</template>
<script>
export default {
name: 'app'
}
</script>
<style>.</style>
Copy the code
The above completes the basic use of vue-router. After starting the service, enter the home page and click router-link to jump to the route. The router-view component will be replaced with the component of routes defined. The official version is to map components to routes and then tell the Vue Router where to render them. So let’s think about how it works.
Second, think about the principle from usage
1. Import and mount
From the point of view of the call sequence, the first is to introduce vue-router module, and then use vue. use(router) for plug-in installation, read the official document of vue.js: cn.vuejs.org/v2/guide/pl…
Vue.js plug-ins should expose an install method. The first argument to this method is the Vue constructor, and the second argument is an optional option object.
Create a vue-router folder, create index.js, and declare a class VueRouter. See developer.mozilla.org/zh-CN/docs/…
// index.js
import install from './install'
export default class VueRouter{}
// Instance attributes must be defined in class methods
// Install can be defined in two ways
// 1. Class attributes can be defined as static in the class
export default class VueRouter{
static install = install
}
// 2
VueRouter.install = install;
Copy the code
Now that the infrastructure is complete, what did you do with install? Create install.js in the same directory
What does a plug-in do when it is installed? There are several points.
- Add a global method or property
- Adding a Global Resource
- Injection component options
- Add Instance method
So after vue.use (Router) is done,
- Add a global method or property as added
$route
with$router
; - Add a global resource as a component
router-view
androuter-link
; - Inject component options with vue. mixin mixed with the beforeCreate handler to provide routing operations during component initialization.
// install.js
import View from './components/view'
import Link from './components/link'
export let _Vue;
export function install (Vue) {
// Vue is passed in when use is used. The Vue constructor is stored and referenced where needed
_Vue = Vue;
// Inject component options
Vue.mixin({ // Add the beforeCreate method to the life cycle of all components, which is mounted on the VUE prototype and executed for each vUE component loaded
beforeCreate() {
if (this.$options.router) { // If there is a router attribute, it is the root instance
this._routerRoot = this; // Mount the root instance on the _routerRoot property
this._router = this.$options.router; // Mount the current router instance on _router
this._router.init(this); // Route initialization is performed on the root instance, where this refers to the router instance
Vue.util.defineReactive(this.'_route'.this._router.history.current);
} else { // The parent component renders the child component
this._routerRoot = this.$parent && this.$parent._routerRoot; }}});// Add two attributes
The value for this._routerRoot will be returned after the beforeCreate command is executed, and the value for the new Vue will not be returned until the new Vue command is executed
$route = this.$route = this.$route = this.$route
Object.defineProperty(Vue.prototype,'$route', {get(){
return this._routerRoot._route; }});// Use this.$router to get an instance of the router, which is the same as new Router()
Object.defineProperty(Vue.prototype,'$router', {get(){
return this._routerRoot._router; }})// Added two global resource components
Vue.component('RouterView',View);
Vue.component('RouterLink',Link);
}
Copy the code
2. New vueRouter parses the user configuration file
Now that we’re done with install, what does vueRouter do when it instantiates
- (1) Establish a matcher for relation matching and provide match and Addroutes methods
- (2) create a history type based on mode to perform path listening and matching.
- (3) Register hook function (there are several kinds of hook function, take one to demonstrate)
// index.js
import {install} from './install'
import createMatcher from './create-matcher'
import HashHistory from './history/hash'
export default class VueRouter{
constructor (options) {
// To create a match based on the routes passed by the user, this.matcher requires two methods
// match: The match method is used to match rules
// addRoutes: used to addRoutes dynamically
this.matcher = createMatcher(options.routes || []);
// Create a history type based on mode, default hash
this.history = new HashHistory(this);
// Register the before check subroutines
this.beforeHooks = [];
}
// this function is called when the component is initialized
init(app){... }beforeEach(fn){
this.beforeHooks.push(fn);
}
}
VueRouter.install = install;
Copy the code
(1) Because of the complex functions, separate the createMatcher, create a create-matcher folder at the same level, and create index.js under the folder
// create-matcher/index.js
import createRouteMap from './create-route-map'
export default function createMatcher(routes){
// Collect all the routing paths, collect the corresponding rendering relationship of the paths
// pathList = ['/','/about','/about/a','/about/b']
// pathMap = ['/':'/ record ','/about':'/about record '...]
let {pathList, pathMap} = createRouteMap(routes);
console.log(pathList, pathMap)
// This method is used to load routes dynamically
function addRoutes(routes){
createRouteMap(routes,pathList,pathMap)
}
function match(location){... }return {
addRoutes,
match
}
}
Copy the code
Create create-route-map.js under create-matcher
// create-matcher/create-route-map.js
export default function createRouteMap(routes,oldPathList,oldPathMap){
// There is no pathList and pathMap when it is first loaded
let pathList = oldPathList || [];
let pathMap = oldPathMap || Object.create(null);
routes.forEach(route= > {
addRouteRecord(route,pathList,pathMap);
});
return {
pathList,
pathMap
}
}
// pathList = ['/','/about','/about/a','/about/b']
// pathMap = ['/':'/ record ','/about':'/about record '...]
function addRouteRecord(route,pathList,pathMap,parent){
let path = parent?`${parent.path}/${route.path}`:route.path;
let record = {
path,
component:route.component,
parent
}
if(! pathMap[path]){ pathList.push(path); pathMap[path] = record; }// Process child routes
if(route.children){
route.children.forEach(r= >{ addRouteRecord(r,pathList,pathMap,route); }}})Copy the code
(2) Implement path listening and matching, create a new folder named “history” in the same directory, and create “base.js” in the folder. Because there are many kinds of patterns, such as “history”, “hash” and “abstract”, we put the basic methods in the basic “history” class, so we think about which are the basic functions and which are unique to the pattern. Basic functions jump, update, monitor; The hash mode also changes the/path to /#/ after the project is started
// history/base.js
import {runQueue} from '.. /util/async'
export default class History{
constructor(router){
this.router = router;
this.current = createRoute(null, {path:'/'});
this.cb = null;
}
// Jump function
transitionTo(location,onComplete){
let route = this.router.match(location);
if(location === route.path && route.matched.length === this.current.matched.length){
return
}
// this. UpdateRoute (route); onComplete && onComplete();
let queue = [].concat(this.router.beforeHooks);
const iterator = (hook,next) = >{
hook(route,this.current,() = >{
next();
})
}
runQueue(queue,iterator,() = >{
this.updateRoute(route); onComplete && onComplete(); })}updateRoute(route){
this.current = route;
this.cb && this.cb(route);
}
listen(cb){
this.cb = cb; }}export function createRoute(record,location){
let res = [];
if(record){
while(record){
res.unshift(record);
record = record.parent
}
}
return {
...location,
matched: res
}
}
Copy the code
Create hash.js and inherit Histoty
// history/hash.js
import History from "./base";
export default class HashHistory extends History{
constructor(router){
super(router);
ensureSlash();
}
getCurrentLocation(){
return getHash();
}
setupListener(){
window.addEventListener('hashchange'.() = >{
this.transitionTo(getHash()); })}push(location){
this.transitionTo(location)
}
}
function ensureSlash(){
if(window.location.hash){
return
}
window.location.hash = '/'
}
function getHash(){
return window.location.hash.slice(1);
}
Copy the code
(3) Register hook functions
// index.js
export default class VueRouter{
constructor (options) {...this.beforeHooks = []; }...init(app){... }beforeEach(fn){
this.beforeHooks.push(fn); }... } VueRouter.install = install;Copy the code
Create a new util method that executes the ticker function
// util/async.js
export function runQueue (queue, iterator, cb) {
function step (index) {
if(index >= queue.length){
cb();
} else {
let hook = queue[index];
iterator(hook,()=>{
step(index+1)
})
}
}
step(0)
}
Copy the code
This is made clearer by combining the runQueue in the transitionTo method in History.
3. Provide init method to listen for changes and redirect routes
// index.js
init(app){
const history = this.history;
// Set the path change listener
const setupHashListener = () = >{
history.setupListener();
}
// Register the route change function
history.listen((route) = >{
app._route = route
})
// Redirect route
history.transitionTo(
history.getCurrentLocation(),
setupHashListener
)
}
Copy the code
4. Then provide other push methods for routing operations
// index.js
push(location){
this.history.push(location)
}
Copy the code
5. Implement router-view components
Create a Components folder
// components/view.js
export default {
functional: true.render(h,{parent,data}){ // Dynamic rendering
let route = parent.$route;
let depth = 0;
data.routerView = true;
while(parent){
if(parent.$vnode && parent.$vnode.data.routerView){
depth++;
}
parent = parent.$parent;
}
let record = route.matched[depth];
console.log('record:',record);
if(! record){return h();
}
returnh(record.component,data); }}Copy the code
Implement the router-link component
// components/link.js
export default {
props: {to: {type:String.required:true
},
tag: {type:String}},render(){
let tag = this.tag || 'a';
let handler = () = >{
this.$router.push(this.to);
}
return <tag onClick={handler}>{this.$slots.default}</tag>}}Copy the code