vue = & gt; 【Wheel Series】 Hand write a mini vue-router

Posted May 26, 20205 min read

mini vue router

  1. Implement a plugin:contains VueRouter class & required install method;
  2. Implement 2 global components:router-link & router-view;
  3. Monitor the change of url and implement hash mode to jump;
  4. Realize jump display of nested sub-routes;

Start to realize

Plugin implementation

When using vue-router, we use Vue.use(VueRouter) to register the route, and receive the routing configuration through new VueRouter(routes), construct the router instance router and mount it to the root instance option, so that all components can pass:this. $router access router

let Vue
//Save routing configuration options
class VueRouter {//new VueRouter(routes)
    constructor(options) {
        this. $options = options
        //todo
    }
}

VueRouter.install = function(_Vue) {//
    Vue = _Vue //Accept the Vue of the host environment
    Vue.mixin({
        beforeCreate() {
            //Mount the router instance on the root component to the Vue prototype, then all component instances will have $router
            if(this. $options.router) {
                Vue.prototype. $Router = this. $Options.router
            }
        },
    })
    //todo
}

export default VueRouter

This wheel uses hash mode, so the url will have a # sign.
The purpose of the router-link component is to click to jump, which can simplify the idea to:
<router-link to ="/about "> About </router-link> => <a href={'#'+this.to}> {this. $slots.default} </a>
Code above:

    const Link = Vue.extend({
        props:{
            to:{
                type:String,
                required:true
            }
        },
        render(h) {
            return h('a', {
                attrs:{
                    href:'#' + this.to
                }
            }, [this. $slots.default]
           )
        }
    })
    //Register router-link
    Vue.component('router-link', Link)

Monitor url changes and implement router-view response view

  1. Obtain the changed URL identifier by monitoring the hashchange event, and create a responsive matched array in the router to record the route matched by the URL change;
  2. Depth mark each router-view component, then take the component to be displayed from the router's matched array according to the current access depth(depth) and render it in the current router-view. The idea is as follows:

Nested route(1) .png
The code is implemented as follows:

 constructor(options) {
        //Responsive matched stores routing configuration by depth
        Vue.util.defineReactive(this, 'matched', [])

        //The current URL identifier recorded by this.current
        const initPath = window.location.hash.slice(1) || '/'
        Vue.util.defineReactive(this, 'current', initPath)

        //Monitor URL changes
        window.addEventListener('hashchange', this.onHashChange.bind(this))
        //When the page loads, it also needs to match the current URL of the component that needs to be rendered
        window.addEventListener('load', this.onHashChange.bind(this))
        this.match()
    }
    onHashChange() {
        this.current = window.location.hash.slice(1) || '/'
        this.matched = []
        this.match()
    }
    match(routes) {
        routes = routes || this. $options.routes
        //Recursively traverse to record all hit routes under the current URL
        for(const route of routes) {
            if(route.path === '/' && this.current === '/') {//Home
                this.matched.push(route)
                return
            }
            //about/info
            if(route.path! == '/' && ~ this.current.indexOf(route.path)) {
                this.matched.push(route)
                if(route.children) {
                    this.match(route.children)
                }
                return
            }
        }
    }

    //1. Mark depth
    const View = Vue.extend({
        render(h) {
            this. $vnode.data.routerView = true //Mark the current component as router-view
                let depth = 0
                //Recursively confirm the depth of the current router-view in the component tree
                let parent = this. $parent
                while(parent) {
                    const vnodeData = parent. $vnode && parent. $vnode.data
                    if(vnodeData) {
                        if(vnodeData.routerView) {//Description is a router-view
                            ++ depth
                        }
                    }
                    parent = parent. $parent
                }
                let component = null
                const {matched} = this. $router
                if(matched [depth]) {
                    component = matched [depth].component
                }
                console.log('Current depth:', depth);
                console.log('Currently matched:', this. $router.matched);
                return h(component)

        }
    })

    Vue.component('router-view', View)

So far, a simple mini vue-router has been implemented. We can introduce our own mini-vue-router to use in the project, just like the official vue-router.
Of course, the official library is far more complicated than this wheel. This wheel is designed to understand the core idea of vue-router. For more in-depth research, please read the official source code [ Portal]]( https://github.com/vuejs/vue -router)
The effect of this wheel is as follows:
image.png

Expansion and Thinking

  1. How to implement routing guard
  2. How to implement route caching
  3. How to implement history mode
  4. How to implement routing lazy loading

Notes

The complete code for this wheel:

//1. It is a plugin with VueRouter class and install method
//2. new VueRouter(options) An instance is mounted on the root instance and all components can access the router instance through this. $Router
//3. router-link router-view two global components router-link jump, router-view display content
//4. Monitor url changes monitor haschange || popstate event
//5. Respond to the latest url:create a responsive property current when it changes, get the corresponding component and display
//6. Sub-component depth marking and macth()
let Vue
//Save routing configuration options
class VueRouter {//new VueRouter(routes)
    constructor(options) {
        this. $options = options
        //todo caches a route mapping table

        //Responsive matched stores routing configuration by depth
        Vue.util.defineReactive(this, 'matched', [])

        //The current URL identifier recorded by this.current
        const initPath = window.location.hash.slice(1) || '/'
        Vue.util.defineReactive(this, 'current', initPath)

        //Monitor URL changes
        window.addEventListener('hashchange', this.onHashChange.bind(this))
        //When the page loads, it also needs to match the current URL of the component that needs to be rendered
        window.addEventListener('load', this.onHashChange.bind(this))
        this.match()
    }
    onHashChange() {
        this.current = window.location.hash.slice(1) || '/'
        this.matched = []
        this.match()
    }
    match(routes) {
        routes = routes || this. $options.routes
        //Recursively traverse to record all hit routes under the current URL
        for(const route of routes) {
            if(route.path === '/' && this.current === '/') {//Home
                this.matched.push(route)
                return
            }
            //about/info
            if(route.path! == '/' && ~ this.current.indexOf(route.path)) {
                this.matched.push(route)
                if(route.children) {
                    this.match(route.children)
                }
                return
            }
        }
    }
}

VueRouter.install = function(_Vue) {//
    Vue = _Vue //Accept the Vue of the host environment
    Vue.mixin({
        beforeCreate() {
            //Mount the router instance on the root component to the Vue instance prototype, then all component instances will have $router
            if(this. $options.router) {
                Vue.prototype. $Router = this. $Options.router
            }
        },
    })

    const Link = Vue.extend({
        props:{
            to:{
                type:String,
                required:true
            }
        },
        render(h) {
            return h('a', {
                attrs:{
                    href:'#' + this.to
                }
            }, [this. $slots.default]
           )
        }
    })

    //1. Mark depth
    const View = Vue.extend({
        render(h) {
            this. $vnode.data.routerView = true //Mark the current component as router-view
            let depth = 0
            //Recursively confirm the depth of the current router-view in the component tree
            let parent = this. $parent
            while(parent) {
                const vnodeData = parent. $vnode && parent. $vnode.data
                if(vnodeData) {
                    if(vnodeData.routerView) {//Description is a router-view
                        ++ depth
                    }
                }
                parent = parent. $parent
            }
            let component = null
            const {matched} = this. $router
            if(matched [depth]) {
                component = matched [depth].component
            }
            console.log('Current depth:', depth);
            console.log('Currently matched:', this. $router.matched);
            return h(component)

        }
    })

    Vue.component('router-link', Link)
    Vue.component('router-view', View)
}

export default VueRouter