Principle analysis and practice of Vue router

Posted by billborric on Sun, 07 Nov 2021 02:44:50 +0100

Today, senior brother, let's talk about the implementation principle of Router and how to implement such a plug-in.

Vue Router is the official routing manager of Vue.js. It is deeply integrated with the core of Vue.js, making it easy to build a single page application.
I won't introduce the use of Vue Router too much. You can go to the official website of Vue Router to learn~

Basic use of Vue router plug-in

import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
const router = new Router({routes:[]})
export default router
​
import router from './route'
new Vue({
    render: h => h(APP),
    router
})

As can be seen from the above code, router is also used as a plug-in, so we develop a plug-in during principle practice.

Plug in development ideas

  • Define a router class for all router operations. Define an install method to mount the router to the Vue instance

  • Register the global components router link and router view. The router link component resolves to an a tag, and the router view resolves to a div tag. The content is the component corresponding to the current route

  • Listen to the hashchange event to change the component corresponding to the current route, and listen to the load event to do the same

  • For nested routes, when rendering the route view, first judge the depth of the current route view, that is, the level of the current route view, and then judge the current route when parsing routes. If both the current route and a route in routes are '/' root routes, they are directly placed in the route rendering array. If the current route is not a root route, If a route in the routes contains the current route, it means that the route in the routes array is either the parent route of the current route or the current route. Then, put the route in the routes array into the route rendering array. If there are children after playing, do it recursively. Finally, a route matching array is obtained, which contains the current route and the parent route of the current route, and the subscript of the sub route in the array is equal to the level subscript of the previous route view, so that the component s of the sub route can be correctly rendered to the corresponding route view.

For example, the current routing table is as follows:

routes:[
    {
        path: '/',
        component: () => import ('../views/index.vue')
    },
    {
        path: '/second',
        component: () => import ('../views/second.vue'),
        childrens: [
            {
                path: '/seconde/article',
                component: import ('../view/article.vue')
            }
        ]
    }
]

At this time, there is a router view under the second component to render the sub route - the article component, and there is a parent router view under the app to render the index and second components. Therefore, the router view level under the second component is 1 (initialized to 0). If the browser accesses the route / second/article at this time, it triggers our route matching method, traverses the routes array and compares it with the current route. The current route is not a root route and contains the / second route, so the option with path of / second is pushed into the route rendering array, and then there are children for recursion. Good guy, The current route is exactly equal to / second/article, so it is also pushed into the rendering array. Finally, we get an array containing two routing options, the parent route subscript 0 and the child route subscript 1. Before, we also marked the route view as a hierarchy, so that we can get the rendered component corresponding to the child route view.
~nice

Plug in development

First come to a router folder, and then create an index.js, which is used by our traditional router. There is a crouter.js on it, and then create a crouter.js:

import Link from './cLink'
import View from './cView'
var Vue
​
class cRouter {
  constructor(options) {
        this.$options = options
    this.courrentRoute = window.location.hash.slice(1) || '/'
    //Define a responsive route rendering array
        Vue.util.defineReactive(this,'routeMap',[])
        // Traversal matching route
    this.initRouterMap()
    // Initialize route change event
    this.initRouteChange()
  }
 
  initRouterMap(route) {
        let routes = route || this.$options.routes
        for (const routeItem of routes) {
            if (this.courrentRoute === '/' && routeItem.path === '/') {
                this.routeMap.push(routeItem)
                return
            }
​
            if (
            routeItem.path !== '/'
            && 
            this.courrentRoute.indexOf(routeItem.path) !== -1) {
                this.routeMap.push(routeItem)
                if (routeItem.childrens && routeItem.childrens.length > 0) {
                    this.initRouterMap(routeItem.childrens)
                }
                return
            }
        }
  }
​
  initRouteChange() {
    window.addEventListener('hashchange', this.routeChange.bind(this))
    window.addEventListener('load', this.routeChange.bind(this))
  }
​
  routeChange() {
        this.courrentRoute = window.location.hash.slice(1)
        this.routeMap = []
    this.initRouterMap()
  }
}
​
function install(_Vue) {
  Vue = _Vue
​
  Vue.mixin({
    beforeCreate() {
      if (this.$options.crouter) {
        Vue.prototype.$crouter = this.$options.crouter
      }
    },
  })
​
  Vue.component('router-link', Link)
​
  Vue.component('router-view', View)
}
​
export default {
  cRouter,
  install,
}

cview.js is used to render router view

export default {
    render(h) {
        // Mark yourself as a router view to avoid confusion with other elements
        this.$vnode.data.routerView = true
        let parent = this.$parent
        //The default level is 0
        let routeDeep = 0
​
        while(parent) {
            // Determine whether there is a parent element and the parent element has a value
            const vodeData = parent.$vnode && parent.$vnode.data
            if (vodeData) {
                // If the parent router view is true, its own level increases
                if (vodeData.routerView) {
                    routeDeep++
                }
            }
            //Continue to find the parent element and recurse
            parent = parent.$parent
        }
​
        let component = null
        const route = this.$crouter.routeMap[routeDeep]
        if (route) {
            component = route.component
        }
        return h(component)
    }
}

cLink.js is used to render router link

export default {
    props: {
        to: {
            type: String,
            default: '/',
        },
    },
    render(h) {
        return h(
            'a',
            { attrs: { href: `#${this.to}` } },
            this.$slots.default
        )
    }
}

So far in this article, we have simply implemented a function similar to vue aouter routing.

What the elder martial brother wants to say is: now the open source framework greatly facilitates our development efficiency, but the simple use of the tripartite framework can not let us learn more knowledge. We should study and explore its implementation principle and design concept, and think about what knowledge we need to master and how to design if we design a framework?

I think this kind of study can learn more knowledge~

Topics: Javascript Front-end Vue.js