Principle and implementation of Vue router

Posted by wolfrat on Wed, 22 Dec 2021 09:12:33 +0100

1, Vue router needs to realize the following functions:

1. vue plug-in:

vue plug-in will call vue The use () method receives a parameter. If the passed parameter is a function, call the function directly; If it is an object, the install method of the object is called. And pass in the vue instance

2. Instance objects router and route

When initializing the Vue instance, the router is passed in, and the Vue instance will generate $route and $router attributes: $route stores the current routing rules and $router stores the routing instance object. You can call some routing related methods.

3. The two components are router view and router link

4. Dynamic routing

cosnt routes = [
    {
        // Use dynamic routing
        path: '/detail/:id',
        name: 'Detail',
        // When props is enabled, the parameters in the URL will be passed to the component
        // Receive URL parameters through props in the build
        props: true,
        // Route lazy loading
        component: () => import('../views/Detail.vue')
    }
]

5. Routing parameters

① Obtain data through the current routing rule {{ $route.params.id }},This method strongly depends on routing. When using components, there must be a route to pass parameters.

② Enable in routing rule props ,Routing will URL The parameters in are passed to the corresponding component, and the component receives this parameter props It's OK. This is the same way that parent-child components pass values. It no longer depends on routing. props: ['id']

6. Nested routing

cosnt routes = [
    // Nested Route 
    {
        path: '/',
        component: Layout,
        children: [
            {
                path: '',
                name: 'Index',
                component: Index
            },
            {
                // Path can use either a relative path or an absolute path
                // path: '/detail/:id',
                path: 'detail/:id',
                name: 'Detail',
                props: true,
                component: () => import('../views/Detail.vue')
            }
        ]
    }
]

7. Programming navigation

this.$router.replace('/login') , This history will not be recorded

this.$router.push({ name: 'Detail', params: { id: 1 } })

this.$router.go(-2),If incoming-1,Equivalent to this.$router.back()

2, Hash mode and History mode

1. Differences between Hash mode and History mode:

Differences in manifestations:

The Hash mode has #, and the Hash number is followed by the routing address

The History mode is an ordinary URL, which needs to be supported by the server

Principle difference:

The Hash pattern is based on anchor points and onhashchange events

The History pattern is based on the History API in HTML5

history. Pushstate(), which can only be supported after ie10 (no request will be sent, but the routing address will be changed and recorded in the history)

history.replaceState()

2. History mode

The server should return the index of the single page application except for static resources html

When clicking a hyperlink:

Click the hyperlink to call history Pushstate() changes the address in the browser's address bar, but does not send a request and stores the address in the history. These are done on the client.

When refreshing the browser:

The browser sends a request to the server. The requested address is the address in the address bar. If the server does not process this address, an error will occur; If the server supports History, when the server judges that the current routing address does not exist, it will index the first page of the single page application Html is returned to the browser. After receiving the page, the browser determines the routing address, and then loads the corresponding component content for rendering

Three. Simulation implementation of Vue Router

1. Vue pre knowledge:

Plug in, mixed in, Vue Observable (), slot, render function, runtime, and full version of Vue

2. Implementation principle of Vue Router

Hash mode:

① The # following content in the URL is used as the path address. If only the # following address is changed, the browser will not send a request and will record this address in the browser access history;

② Listen for hashchange events. When the hash is changed, the hashchange event will be triggered and the current routing address will be recorded;

③ Find the corresponding component according to the current routing address and re render.

History mode:

① Through history The pushstate () method changes the address bar, only changes the address and records the routing address without sending a request

② By listening to the pop state event, you can listen to the changes of the browser operation and record the changed address. It will not be triggered when calling pushState or replaceState. The browser's forward and backward buttons or back and forward methods will be triggered

③ Find the corresponding component according to the current routing address and re render

3. Vue Router use case code

// Register plug-ins
// Vue.use() internally calls the install method of the incoming object
Vue.use(VueRouter)
// Create routing object
const router = new VueRouter({
    routes: [
        { name: 'home', path: '/', component: homeComponent }
    ]
})
// Create a Vue instance and register the router object
new Vue({
    router,
    render: h => h(App)
}).$mount('#app')
​

4. Realization idea

① Create a vueroter plug-in and use the static method install

Determine whether the plug-in has been loaded

When Vue is loaded, mount the incoming router object on the Vue instance (Note: it is executed only once)

② Create vueroter class

Initialization, options, routeMap, data (simplify operation, create Vue instance as responsive data record current path)

Create a routing map, traverse all routing information, and record the mapping of components and routes in the routeMap object

Create router link and router view components

When the path changes, find the corresponding component in the routerMap object through the current path and render the router view

Register the pop state, hashchange and load events. When the routing address changes, re record the current path

5. Code implementation

① Create vueroter plug-in

static install (Vue) {  // When this method is called, the Vue constructor is passed in
    // 1. Determine whether the current plug-in has been installed
    if (VueRouter.install.installed) return
    VueRouter.install.installed = true
    // 2. Record the Vue constructor to a global variable
    _Vue = Vue
    // 3. Inject the router object passed in when creating the Vue instance into the Vue instance
    // Mixed in, all Vue instances and components will execute this method
    _Vue.mixin({
        beforeCreate() {
            if (this.$options.router) {  // This attribute is only available in the Vue instance option, and the component does not
                _Vue.prototype.$router = this.$options.router
                // Initialization component, routing map and event registration are implemented in sections 3, 4 and 5
                this.$options.router.init()
            }
        }
    })
}

② Implementation constructor

constructor (options) {     // Initialize three properties
    this.options = options
    this.routeMap = {}
    this.data = _Vue.observable({   // The data attribute is responsive and automatically updates the view when the route changes
        current: '/'
    })
}

③ Implement createroutemap()

creatRouteMap () {      // Create routing map
    // Traverse all routing rules, parse the routing rules into key value pairs and store them in routeMap
    this.options.routes.forEach(route => {
        this.routeMap[route.path] = route.component
    })
}

④ Implement router link and router view components

// Build versions of Vue include the runtime version and the full version
// Runtime version: template template is not supported. It needs to be compiled in advance when packaging
// Full version: including runtime and compiler. The volume is about 10KB larger than the runtime version. When the program runs, the template is converted into render function
initComponents () {     // Create two components, router link and router view
    _Vue.component('router-link', {
        props: {
            to: String
        },
        // 1 - full version of Vue 
        //Need to be in Vue config. Runtimecompiler is configured in JS: true, and the template will be automatically parsed into render function at runtime
        // template: "<a :href='to'><slot></slot></a>"
        // 2 - Vue at runtime -- use render function directly
        render (h) {
            // The h function receives three parameters. The first one is the target for generating dom elements, the second is some option settings, and the third is the content
            return h('a', { 
                attrs: {
                    href: this.to
                },
                on: {
                    click: this.handleClick
                }
            }, [this.$slots.default])
        },
        methods: {
            handleClick(e) {
                // pushState receives three parameters, 1 - event object, 2 - Web page title, 3 - address of hyperlink jump in
                history.pushState({}, '', this.to)
                this.$router.data.current = this.to
                // Cancel the default behavior of a tag to prevent page refresh from making requests to the server
                e.preventDefault()
            }
        }
    })
    const that = this
    _Vue.component('router-view', {
        render (h) {
            // Find the corresponding component according to the current path. Pay attention to this problem
            const component = that.routeMap[that.data.current]
            return h(component)
        }
    })
}

⑤ Implement registration events

initEvents() {
    // For the problem of path change, retrieve the current path and record it in the current in data
    window.addEventListener('hashchange', this.onHashChange.bind(this))
    // Problem with refresh button
    window.addEventListener('load', this.onHashChange.bind(this))
    // Problem with forward and backward buttons
    window.addEventListener('popstate', this.onHashChange.bind(this))
}

⑥ Implement init()

// It is convenient to initialize when mixing
init() {
    this.creatRouteMap()
    this.initComponents()
    this.initEvents()
}

be careful:

Projects created by Vue cli use the runtime version of Vue. Cli by default js

If you want to switch to Vue with compiler version JS, you need to modify the Vue cli configuration

Create Vue. From the project root directory config. JS file, add runtimeCompiler

module.exports = {
	runtimeCompiler: true
}

6. Specific source code

let _Vue = null
export default class VueRouter {
    static install (Vue) {  // When this method is called, the Vue constructor is passed in
        // 1. Determine whether the current plug-in has been installed
        if (VueRouter.install.installed) return
        VueRouter.install.installed = true
        // 2. Record the Vue constructor to a global variable
        _Vue = Vue
        // 3. Inject the router object passed in when creating the Vue instance into the Vue instance
        // Mixed in, all Vue instances and components will execute this method
        _Vue.mixin({
            beforeCreate() {
                if (this.$options.router) {  // This attribute is only available in the Vue instance option, and the component does not
                    _Vue.prototype.$router = this.$options.router
                    this.$options.router.init()
                }
            }
        })
    }

    constructor (options) {     // Initialize three properties
        this.options = options
        this.routeMap = {}
        this.data = _Vue.observable({   // The data attribute is responsive
            current: '/'
        })
    }

    init() {
        this.creatRouteMap()
        this.initComponents()
        this.initEvents()
    }

    creatRouteMap () {      // Create routing map
        // Traverse all routing rules, parse the routing rules into key value pairs and store them in routeMap
        this.options.routes.forEach(route => {
            this.routeMap[route.path] = route.component
        })
    }

    // Build version of Vue
    // Runtime version: template template is not supported. It needs to be compiled in advance when packaging
    // Full version: includes runtime and compiler,
    initComponents () {     // Create two components, router link and router view
        _Vue.component('router-link', {
            props: {
                to: String
            },
            // 1 - full version of Vue 
            // template: "<a :href='to'><slot></slot></a>"
            // 2 - during operation
            render (h) {
                // The h function receives three parameters
                return h('a', { 
                    attrs: {
                        href: this.to
                    },
                    on: {
                        click: this.handleClick
                    }
                }, [this.$slots.default])
            },
            methods: {
                handleClick(e) {
                    // pushState receives three parameters, 1 - event object, 2 - Web page title, 3 - address of hyperlink jump in
                    history.pushState({}, '', this.to)
                    this.$router.data.current = this.to
                    // Cancel the default behavior of a tag to prevent page refresh from making requests to the server
                    e.preventDefault()
                }
            }
        })
        const that = this
        _Vue.component('router-view', {
            render (h) {
                // Find the corresponding component according to the current path. Pay attention to this problem
                const component = that.routeMap[that.data.current]
                return h(component)
            }
        })
    }

    initEvents() {
        // For the problem of path change, retrieve the current path and record it in the current in data
        window.addEventListener('hashchange', this.onHashChange.bind(this))
        // Problem with refresh button
        window.addEventListener('load', this.onHashChange.bind(this))
        // Problem with forward and backward buttons
        window.addEventListener('popstate', this.onHashChange.bind(this))
    }
    // Retrieve the current path and record it to the current in data
    onHashChange() {
        this.data.current = window.location.pathname || '/'
    }
}

Topics: Front-end Vue