The paper

The advantages and disadvantages of a single page are not covered in this article, only the principle. There are two ways to implement single-page functionality:

  1. Use the anchor point hashHistory
  2. Take advantage of the browser’s browserHistory principle

HashHistory principle

Add hashchange listening

window.addEventListener(
    'hashchange'.function() {
        // Change the page when the hash changes})Copy the code

Change the hash

window.location.hash = 'aaa';
Copy the code

BrowserHistory principle

Add popState listener to listen to browser forward and backward. However, you cannot listen to pushState and replaceState. Therefore, you need to replace the page when pushState and replaceState are executed.

window.addEventListener(
    'popstate'.function() {
        // Change the page when the URL changes})Copy the code

To change urls, see MDN for pushState and replaceState documentation.

history.pushState({}, ' ', path);
history.replaceState({}, ' ', path);
Copy the code

Vue – the practice of the Router

The Router class collects data to determine the route type

  1. Mode: determines the routing mode. Instantiate HTML5History or HashHistory.
  2. Routes: Collects all routing information.
class Router {
    constructor(options) {
        this.options = options;
        let mode = options.mode || 'hash';
        this.routes = options.routes;
        switch (mode) {
            case 'history':
              this.history = new HTML5History(this, options.base)
              break
            case 'hash':
              this.history = new HashHistory(this, options.base, this.fallback)
              break
            default:
              / / an error}}... }Copy the code

Second, the HTML5History

  1. The setupListeners method listens on popState to get the current path
  2. The push method changes the route
class HTML5History {
    constructor (route, base) {
        this.current = ' ';
    }
    setupListeners() {
        const handleRoutingEvent = () = > {
            this.current = location.pathname? .slice(1) | |'/';
        }
        window.addEventListener(
            'popstate',
            handleRoutingEvent
        )
    }
    push (path) {
        history.pushState({}, ' ', path); }}Copy the code

Third, HashHistory

  1. The setupListeners method listens on the Hashchange to get the current path
  2. The push method changes the route
class HashHistory {
    constructor (route, base) {
        this.current = ' ';
    }
    setupListeners() {
        const handleRoutingEvent = () = > {
            this.current = location.hash? .slice(1) | |'/';
        }
        window.addEventListener(
            'hashchange',
            handleRoutingEvent
        )
    }
    push (location) {
        window.location.hash = location
    }
}
Copy the code

4. Trigger setupListeners

The Router will have an init initialization to perform listening; The push method performs route modification.

class Router {
    init() {
        this.history.setupListeners()
    }
    push (location) {
        this.history.push(location)
    }
}
Copy the code

To protect the reading experience, disconnect, explain how vue uses the plugin, and then go back to vue-Router

Using Router (see main.js)

The router appears in app.$options

const app = new Vue({
  el: '#app',
  router,
  store,
  render: h= > h(App)
})
console.log(app)
Copy the code

Understand the Vue. Use

Vue. Use (Router) {install (Router) {install (Router) {Vue (Router) {install (Router)}}

import Router from '@/utils/router'
Vue.use(Router)
Copy the code

Static method install

  1. In the beforeCreate phase before component instantiation, assign the instance value to this._routerRoot and execute init to trigger step 4 above.
  2. To set up hijacking, access this.$router and return this. _routerroot. _router
  3. Added global component router-view to render routing components
Router.install = function(Vue) {
    Vue.mixin({
        beforeCreate () {
            if (this.$options.router) {
                this._routerRoot = this
                this._router = this.$options.router
                this._router.init(this)}else {
                this._routerRoot = (this.$parent && this.$parent._routerRoot) || this}}})Object.defineProperty(Vue.prototype, '$router', {
        get () { return this._routerRoot._router }
    })
    Vue.component('router-view', {render(h){
            return h(com) // com is content component}})}Copy the code

Now we have set the route listener and registered the router-view component, so how to render the corresponding component near the router-view component when the route changes?

6. Router-view content switching

  1. Declare a _Vue in the external scope and assign Vue to _Vue when install executes.
let _Vue;

Router.install = function(Vue) {
    _Vue = Vue;
}
Copy the code
  1. HashHistory, HTML5History creates instances using _Vue
class HashHistory {
    constructor (route, base) {
        this.current = ' ';
        this.app = new _Vue({
            data() {
                return {
                    path: '/'}},})}}class HTML5History {
    constructor (route, base) {
        this.current = ' ';
        this.app = new _Vue({
            data() {
                return {
                    path: '/'}},})}}Copy the code
  1. The router-view component uses path in the instance created in Step 2 to filter all routing information and find corresponding components. Because the router-view component uses app.path, it adds a dependency. When app.path changes, the router-view component reexecutes.
Router.install = function(Vue) {... Vue.component('router-view', {render(h){
            const path = this._routerRoot._router.history.app.path;
            const routes = this._routerRoot._router.routes;
            const route = routes.find((i) = > i.path === ` /${path}`)
            const com = route ? route.component : routes.find((i) = > i.path === ` ` / 404).component
            return h(com)
        }
    })
    
}
Copy the code
  1. Listen for route changes and change this.app.path to reroute the router-view component. PushState does not trigger popState listening, so change this.app.path separately
class HTML5History {...setupListeners() {
        const handleRoutingEvent = () = > {
            this.current = location.pathname? .slice(1) | |'/';
            this.app.path = this.current;
        }
        window.addEventListener(
            'popstate',
            handleRoutingEvent
        )
    }
    push (path) {
        history.pushState({}, ' ', path);
        this.app.path = path; }}class HashHistory {...setupListeners() {
        const handleRoutingEvent = () = > {
            this.current = location.hash? .slice(1) | |'/';
            this.app.path = this.current;
        }
        window.addEventListener(
            'hashchange',
            handleRoutingEvent
        )
    }
    push (location) {
        window.location.hash = location
    }
}
Copy the code

The complete code

let _Vue;



class HTML5History {
    constructor (route, base) {
        this.current = ' ';
        this.app = new _Vue({
            data() {
                return {
                    path: '/'}}})}setupListeners() {
        const handleRoutingEvent = () = > {
            this.current = location.pathname? .slice(1) | |'/';
            this.app.path = this.current;
        }
        window.addEventListener(
            'popstate',
            handleRoutingEvent
        )
    }
    push (path) {
        history.pushState({}, ' ', path);
        this.app.path = path; }}class HashHistory {
    constructor (route, base) {
        this.current = ' ';
        this.app = new _Vue({
            data() {
                return {
                    path: '/'}}})}setupListeners() {
        const handleRoutingEvent = () = > {
            this.current = location.hash? .slice(1) | |'/';
            this.app.path = this.current;
        }
        window.addEventListener(
            'hashchange',
            handleRoutingEvent
        )
    }
    push (location) {
        window.location.hash = location
    }
}
class Router {
    constructor(options) {
        this.options = options;
        let mode = options.mode || 'hash';
        this.routes = options.routes;
        switch (mode) {
            case 'history':
              this.history = new HTML5History(this, options.base)
              break
            case 'hash':
              this.history = new HashHistory(this, options.base, this.fallback)
              break
            default:
              / / an error}}init() {
        this.history.setupListeners()
    }
    push (location) {
        this.history.push(location)
    }
}
Router.install = function(Vue) {
    _Vue = Vue;
    Vue.mixin({
        beforeCreate () {
            if (this.$options.router) {
                this._routerRoot = this
                this._router = this.$options.router
                this._router.init(this)}else {
                this._routerRoot = (this.$parent && this.$parent._routerRoot) || this}}})Object.defineProperty(Vue.prototype, '$router', {
        get () { return this._routerRoot._router }
    })

    Object.defineProperty(Vue.prototype, '$route', {
        get () { return this._routerRoot._route }
    })
    Vue.component('router-view', {render(h){
            const path = this._routerRoot._router.history.app.path;
            const routes = this._routerRoot._router.routes;
            const route = routes.find((i) = > i.path === ` /${path}`)
            const com = route ? route.component : routes.find((i) = > i.path === ` ` / 404).component
            return h(com)
        }
    })
    
}
export default Router;

Copy the code