Mind mapping

Vue-router provides two components, router-link router-view, and two prototype attributes, $route $Router

start

Vue. Use (Router, Vue. Start right there

Usage scenarios

import Vue from 'vue' import Router from ".. /vue-router" import routes from './routes' Vue.use(Router) let router = new Router({ routes })Copy the code

index

Js as a VueRouter class, and then import the install method. Since the vue-Router install method is a bit more complicated than Vuex, we will use install as a separate file.

import install from './install';

class VueRouter {
    constructor(options) {
      
    }

}
VueRouter.install = install;

export default VueRouter
Copy the code

Vue.use()

Use () to determine whether install is a function of the registered plugin. If so, execute the install function. Or determine whether the plug-in itself is a function, and execute the plug-in itself. There is essentially no difference here, with or without install. VueRouter uses Install in order to use install as an entry function for easy encapsulation and to separate install from the rest of the code.

if (typeof plugin.install === 'function') {
      plugin.install.apply(plugin, args)
 } else if (typeof plugin === 'function') {
      plugin.apply(null, args)
 }
Copy the code

install

Now that VueRouter has an instance of vue.use(), the install function in the VueRouter class will be executed. Install uses vue. mixin and blends it into the current component life cycle beforeCreate. The following code blends it into the current component life cycle beforeCreate. The logic is to determine whether the current component is the root component, if so, the _routerRoot key is placed in the current component and the value is the Vue instance. Put _router as the key in the current component with the value of VueRouter instance. Then perform initialization. If the current component is not a root component, the component is a child of the root component. Put _routerRoot as the key in the current component, and the value is _routerRoot of the parent component, which is obtained from the parent. Visited. Then register the global components router-view and router-link

import RouterView from '.. /components/router-view' import RouterLink from '.. /components/router-link' const install = (_Vue, Mixin ({beforeCreate() {if (this.$options.router) {this. This._router = this.$options.router (); this. // initialize _vue.util. defineReactive(this, '_route', } else {this._routerRoot = this.$parent && this.$parent._routerRoot }}}) object.defineProperty (_vue. prototype, '$route', {// proxy $route get() {return this._routerroot._route}}) object.defineProperty (_vue.prototype, '$router', {// proxy $router get() {return this._routerroot._router}}) _vue.Component ('router-view', RouterView) // Register router-view_vue. component('router-link', RouterLink) // Register router-view} export default InstallCopy the code

init

Vue-router defaults to hash mode — the hash of a URL is used to simulate a full URL so that the page does not reload when the URL changes.

And then we go back to the VueRouter class, and now we have an init function. The current routing method is hash, and there are two other methods, history. Vue-router creates a base class to perform the same logic, and subclasses inherit the base class in all three ways, and then execute their own code. Get the history instance with new HashHistory and initialize init to execute the function corresponding to the history instance. Looking at the history instance, these functions come from base.js and hash.js.

import install from './install'; class VueRouter { constructor(options) { this.history = new HashHistory(this); } init(app) { const history = this.history; const setupHashLister = () => { history.setupLister(); / / jump of hash} history. TransitionTo (history. GetCurrentLocation (), setupHashLister ) history.listen((route) => { app._route = route }) } } VueRouter.install = install; export default VueRouterCopy the code

hash.js

For a brief introduction to hash functions, see constructor, which assigns router as its parent class. Execute the ensureSlash function because the hash function will have multiple # s upon entering the page compared to other functions. So let’s do that at initialization. The getCurrentLocation function gets the current path, the push function is a hash jump, and the setupLister function is the listener hashchange described earlier.

import Histroy from './base';

function ensureSlash() {
    if (window.location.hash) {
        return
    }
    window.location.hash = '/';
}


class HashHistory extends Histroy {
    constructor(router) {
        super();
        this.router = router;
        ensureSlash();
    }
    getCurrentLocation() {
        return window.location.hash.slice(1);
    }
    push(location){
        this.transitionTo(location,()=>{
            window.location.hash = location
        })
    }
    setupLister() {
        window.addEventListener('hashchange', () => {
            this.transitionTo(window.location.hash.slice(1));
        })

    }

}
export default HashHistory
Copy the code

base.js

Construcror (VueRouter, curent); construcror (VueRouter, curent); Construcror (VueRouter, curent); 2. Cb is History’s Listen () function, which places $route on the current component for the user to use. 3. Callback is the setupHashLister() function that performs HashHistory, which adds the listener event onhashChange to the current window. OnhashChange then performs hashchange transitionTo update. 4. Finally, assign r to current to update the routing information.

class History { constructor(router) { this.router = router; this.current = createRoute(null, { path: '/' }) } transitionTo(location, callback) { let r = this.router.match(location) if (location == this.current.path && r.matched.length == This. Current. Matched. Length) {/ / prevent repeated jump return} this. Cb && enclosing cb (r); callback && callback(); this.current = r; } listen(cb) { this.cb = cb; }}Copy the code

flat

The this.router.match(location) and createRoute() just implemented in base.js need to be based on flat configuration. The route configuration is as follows. You need to flatten the configuration so that it can be used.

 [
    {
        path: '/',
        name: 'home',
        component: Home
    },
    {
        path: '/about',
        name: 'about',
        component: About,
        children: [
            {
                path: 'add',
                name: 'add',
                component: Add
            },
            {
                path: 'bull',
                name: 'bull',
                component: Bull
            }
        ]
    }
]
Copy the code

This is what it looks like when it flattens

/: {path: "/", Component: {... }, parnent: undefined} /about: {path: "about", Component: {... }, parnent: {... }} /about/add: {path: "add", component: {... }, parnent: {... }} /about/bull: {path: "bull", component: {... }, parnent: {... }}Copy the code

Then look at the flattening function createMatcher and createRouteMap

createMatcher

CreateMatcher returns a match function. The match method matches the path, flattens the configuration of the object based on the path, and then executes the createRoute method, converts it to a route, and returns. The pathMap is generated by createRouteMap

import createRouteMap from './create-route-map'
import { createRoute } from './history/base';

export default function createMatcher(routes) {
    let { pathList, pathMap } = createRouteMap(routes);


    function match(location) {
        console.log(pathMap)
        let record = pathMap[location];
        
        return createRoute(record,{
            path: location
        })
    }

    return {
        match
    }
}

Copy the code

CreateRouteMap (flattening)

Pass the Routes configuration into createRouteMap, traverse routes, and flaten pathMap with path as key name and value as an object that wraps the path, component, and parent component. Match the path to the parent’s path to its own path and recurse if there are children, return all flat.

export default function createRouteMap(routes, oldPathList, oldpathMap) { let pathList = oldPathList || []; let pathMap = oldpathMap || Object.create(null); routes.forEach(route => { addRouteRecord(route, pathList, pathMap); }) return { pathList, pathMap } } function addRouteRecord(route, pathList, pathMap,parnent) { let path = parnent ? `${parnent.path}/${route.path}`: route.path; let record = { path: route.path, component: route.component, parnent } if (! pathMap[path]) { pathList.push(path); pathMap[path] = record; } if(route.children){ route.children.forEach(route=>{ addRouteRecord(route, pathList, pathMap,record) }) } }Copy the code

createRoute

CreateRoute is a function that generates $route, passing in flat configuration, path. Use res as an empty array, and if the flat configuration passed in has a value, do a while loop that inserts itself from the head of the array, takes the parent component and inserts it from the head, and so on, resulting in an array with hierarchical relationship. Returns loAction and array wrapped as objects.

export function createRoute(record, location) { let res = []; if (record) { while (record) { res.unshift(record) record = record.parnent } } return { ... location, matched: res } }Copy the code

router-view

Routerview is a function that returns the Render method to loop through the nested RouterView, depth, and matched index. RouView: $vNode.data. RouView: $vNode.data. RouView: $vNode.data. RouView: $vNode.data. RouView: $vNode.data

export default {
    name:'routerView',
    functional: true,
    render(h,{parent,data}) {
        let route = parent.$route

        let depth = 0;
        while (parent) {  
            if (parent.$vnode && parent.$vnode.data.routeView) {
                depth++;
            }
            parent = parent.$parent;
        }
        data.routeView = true;
        let record = route.matched[depth];
        if (!record) {
            return h();
        }
        return h(record.component, data);
    }
}
Copy the code

router-link

Routerlink returns an A tag, slots the corresponding text, and calls the $Router push method on the added jump event

export default { name: 'routerLink', props: { to: { type: String, required: true }, tag: { type: String, default: 'a' } }, methods: {handler(to) {this.$router. Push (to)}}, render() { return <a onClick={this.handler.bind(this,this.to)}>{this.$slots.default[0].text}</a> } }Copy the code

Vue-router draft: shimo.im/docs/TqKywd…