Vue – principle of the Router

Hash pattern

  • The content after # in the URL is the path address
  • Listen for the Hashchange event
  • Find the corresponding component based on the current routing address and re-render it

The History mode

  • Change the address bar with the history.pushstate () method
  • Listen for popState events
  • Find the corresponding component based on the current routing address and re-render it

Write a Router

The following methods are all disassembly methods and will eventually be reassembled

Analysis of the

Review the core code

Vue.use(VueRouter) Use when an object is passed in internally, the install method of the object is called
// Create a 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')
Copy the code
  • Vue.use calls the install method of the object when it is passed in internally, so let’s deal with the install method first

The install method

  • Install accepts two arguments
    • Constructor of vue
    • Optional option object (we don’t use it here, so we don’t pass it)
  • The method is divided into three steps
    1. If the plug-in is already installed, return directly
    2. Record the constructor of vue in the global variable, because we will use the constructor of vue in VueRouter’s instance method in the future
    3. Inject the Router object passed in when creating the Vue instance
let _Vue = null
export default class VueRouter {
  // Pass in two arguments, one to the vue constructor and the second to the optional option object
  static install (Vue) {
    // 1. Return if the plug-in is already installed
    if (VueRouter.install.installed && _vue === true) return
    VueRouter.install.installed = true // Indicates that the plug-in is installed

    // 2. Record the vue constructor in the global variable, since we will use the vue constructor in VueRouter's instance method in the future
    _Vue = Vue

    // 3. Inject the router object passed into the Vue instance
    Vue.mixin({
      beforeCreate () {
        if (this.$options.router) {
          _Vue.prototype.$router = this.$options.router
        }
      }
    })
  }
}
Copy the code

constructor

Again, we don’t need to do much, just declare three properties

  1. this.options = options Record the option passed in the constructor, options
  2. this.routeMap = {} Routes = routes; routes = routes; routes = routes
  3. this.data = _Vue.observable({ current: ‘/’ })} Observable creates a reactive object that stores the current route address (/ by default)
  constructor (options) {
    // Record the options passed in the constructor
    this.options = options
    // Add routes passed in options. // Add routes
    this.routeMap = {}
    // Data is a responsive object, because data stores the current route address and automatically loads components when the route changes
    this.data = _Vue.observable({ // Observable creates a responsive object
      current: '/' // Stores the current routing address. The default is /})}Copy the code

createRouteMap

Convert routes (routing rules) from the option passed in the constructor to key-value pairs into the key-routing address, value-routing component of the routeMap object

  createRouteMap () {
    // All routing rules are iterated over and stored as key-value pairs in a routeMap object
    this.options.routes.forEach(route= > {
      this.routeMap[route.path] = route.component
    })
  }
Copy the code

initComponents

This method is used to create router-link and router-view

router-link

  initComponents (Vue) {
    Vue.component('router-link', {
      props: {
        to: String
      },
      template: '<a :href="to"><slot></slot></a>'})}Copy the code
Pay attention to the point
  • If we use vue_CLI to create the project and run it, we will get some errors using the above creation method because we used template to create the tag

  • The runtime version of vue used by vue_CLI does not support the Template template and needs to be compiled in advance when packaged

There are two solutions

  1. Modify the vue. Config. js configuration to use the full vue

    It contains both the runtime and the compiler, and is about 10K larger than the runtime version. When the program runs, the template is converted into the render function

    module.exports = {
        // Render the full vue
        runtimeCompiler: true
    }
    Copy the code
  2. Using the render function

    The runtime version of Vue does not come with a compiler, so it does not support the template option in components. The compiler compiles the template into the render function, and the runtime component can write the render function directly

    The single-file component used the render function without template because the single-file template was compiled into the render function during the packaging process, which is called precompilation

render

  • The render function takes an argument, usually called h, to help us create the virtual DOM, and H is passed by vue

  • The h function takes three arguments

    1. Create a selector for this element.
    2. Set some attributes for the tag, and if they are DOM object attributes, add them to Attrs.
    3. The child element of the generated element, so it is in array form
      initComponents (Vue) {
        Vue.component('router-link', {
          props: {
            to: String
          },
    	  /* The runtime version of vue does not come with a compiler, so it does not support the template option in components. The compiler compiles template into the render function. The single-file component always uses the render template because the single-file template was compiled into the render function during the packaging process, which is called precompiling */
          render (h) { // This function takes an argument, usually called h, to help us create the virtual DOM. H is passed by vue
            return h('a', {	The h function takes three arguments: 1. Create a selector for this element. 2. Set some attributes for the tag. If they are DOM object attributes, add them to attrs. 3. A child element of the generated element, so it is an array
              attrs: {
                href: this.to
              }
            }, [this.$slots.default])
          }
          // template: '<a :href="to"><slot></slot></a>'})}Copy the code

router-view

    const self = this
	Vue.component('router-view', {
      // Obtain the routing component corresponding to the current routing address
      render (h) {
        const component = self.routeMap[self.data.current]
        return h(component)
      }
    })
Copy the code

Check for problems by combining the above methods

  • Because we only call the install method by default, but we have a few other methods to call, and we need one moreinitMethod, called after the install method has been calledCreateRouteMap with initComponents () ()
  init () {
    this.createRouteMap()
    this.initComponents(_Vue)
  }
Copy the code
  • Combine all of the above methods (remove comments for ease of reading)

    / /.. /vuerouter/index.js
    let _Vue = null
    export default class VueRouter {
      static install (Vue) {
        if (VueRouter.install.installed && _Vue === true) return
        VueRouter.install.installed = true
    
        _Vue = Vue
    
        Vue.mixin({
          beforeCreate () {
            if (this.$options.router) {
              _Vue.prototype.$router = this.$options.router
              this.$options.router.init()
            }
          }
        })
      }
    
      constructor (options) {
        this.options = options
        this.routeMap = {}
        this.data = _Vue.observable({
          current: '/'
        })
      }
    
      init () {
        this.createRouteMap()
        this.initComponents(_Vue)
    	  this.initEvent()
      }
    
      createRouteMap () {
        this.options.routes.forEach(route= > {
          this.routeMap[route.path] = route.component
        })
      }
    
      initComponents (Vue) {
        Vue.component('router-link', {
          props: {
            to: String
          },
    	  render (h) {
            return h('a', {
              attrs: {
                href: this.to
              }
            }, [this.$slots.default])
          }
        })
        const self = this
        Vue.component('router-view', {
          render (h) {
            const component = self.routeMap[self.data.current]
            return h(component)
          }
        })
      }
    }
    // Remember to replace VueRouter introduced in router/index.js
    import Vue from 'vue'
    // import VueRouter from 'vue-router'
    import VueRouter from '.. /vuerouter'
    import Home from '.. /views/Home.vue'
    import Home from '.. /views/About.vue'
    
    Vue.use(VueRouter)
    
    const routes = [
      {
        path: '/'.name: 'Home'.component: Home
      },
      {
        path: '/about'.name: 'About'.component: About
      }
    ]
    
    const router = new VueRouter({
      routes
    })
    
    export default router
    
    Copy the code
    • After the actual test, we found that there were still some minor issues, namely when we clicked router-link, we changed the path to refresh the page, but in the single-page component, we didn’t need to refresh the page, so we needed to make some minor adjustments to router-Link

    To perfect the router – the link

    • We need to add a method to the A tag that does three things

      1. PushState changes the browser address bar without sending a request to the server
      2. Synchronize the current routing path to the default value
      3. Prevents label default events

      The pushState method takes three arguments

      1. The data.
      2. Title Specifies the title of the page.
      3. The url address.
        Vue.component('router-link', {
          props: {
            to: String
          },
          render (h) {
            return h('a', {
              attrs: {
                href: this.to
              },
    		  on: {
    			  click: this.clickHandler
    		  }
            }, [this.$slots.default])
          },
    	  methods: {
    		  clickHandler (e) {
              // 1. Use pushState to change the browser address bar without sending a request to the server
              history.pushState({}, ' '.this.to)	// This method takes three arguments, 1.data. 2. Title Specifies the title of the page. 3. The url address
              this.$router.data.current = this.to
              e.preventDefault()
    		  }
    	  }
        })
    Copy the code

Final refinement

After perfecting the above router-link, we implemented vue-Router, but we still need to add a small method to make the final improvement, namely the forward and backward function in the upper left corner of the browser

  • This is done by listening to popState to assign the current routing path to current ** (remember to add it to the init method) **
  initEvent () {
	  window.addEventListener('popstate'.() = > {
		  this.data.current = window.location.pathname
	  })
  }
Copy the code
  • Final complete version of the code (in two versions, annotated at the end)
let _Vue = null
export default class VueRouter {
  static install (Vue) {
    if (VueRouter.install.installed && _Vue === true) return
    VueRouter.install.installed = true

    _Vue = Vue

    Vue.mixin({
      beforeCreate () {
        if (this.$options.router) {
          _Vue.prototype.$router = this.$options.router
          this.$options.router.init()
        }
      }
    })
  }

  constructor (options) {
    this.options = options
    this.routeMap = {}
    this.data = _Vue.observable({
      current: '/'
    })
  }

  init () {
    this.createRouteMap()
    this.initComponents(_Vue)
	this.initEvent()
  }

  createRouteMap () {
    this.options.routes.forEach(route= > {
      this.routeMap[route.path] = route.component
    })
  }

  initComponents (Vue) {
    Vue.component('router-link', {
      props: {
        to: String
      },
      render (h) {
        return h('a', {
          attrs: {
            href: this.to
          },
		  on: {
			  click: this.clickHandler
		  }
        }, [this.$slots.default])
      },
	  methods: {
		  clickHandler (e) {
          history.pushState({}, ' '.this.to)
          this.$router.data.current = this.to
          e.preventDefault()
		  }
	  }
    })
    const self = this
    Vue.component('router-view', {
      render (h) {
        const component = self.routeMap[self.data.current]
        return h(component)
      }
    })
  }

  initEvent () {
	  window.addEventListener('popstate'.() = > {
		  this.data.current = window.location.pathname
	  })
  }
}

Copy the code
let _Vue = null
export default class VueRouter {
  // Pass in two arguments, one to the vue constructor and the second to the optional option object
  static install (Vue) {
    // 1. Return if the plug-in is already installed
    if (VueRouter.install.installed && _Vue === true) return
    VueRouter.install.installed = true // Indicates that the plug-in is installed

    // 2. Record the vue constructor in the global variable, since we will use the vue constructor in VueRouter's instance method in the future
    _Vue = Vue

    // 3. Inject the router object passed into the Vue instance
    Vue.mixin({
      beforeCreate () {
        if (this.$options.router) {
          _Vue.prototype.$router = this.$options.router
          this.$options.router.init()
        }
      }
    })
  }

  constructor (options) {
    // Record the options passed in the constructor
    this.options = options
    // Add routes passed in options. // Add routes
    this.routeMap = {}
    // Data is a responsive object, because data stores the current route address and automatically loads components when the route changes
    this.data = _Vue.observable({ // Observable creates a responsive object
      current: '/' // Stores the current routing address. The default is /
    })
  }

  init () {
    this.createRouteMap()
    this.initComponents(_Vue)
	this.initEvent()
  }

  // Convert routes from the option passed in the constructor to key-value pairs in the routeMap object key-routing address, value-routing component
  createRouteMap () {
    // All routing rules are iterated over and stored as key-value pairs in a routeMap object
    this.options.routes.forEach(route= > {
      this.routeMap[route.path] = route.component
    })
  }

  // Create two components, router-link and router-view
  initComponents (Vue) {	// Accept parameters to reduce contact with the outside world
    Vue.component('router-link', {
      props: {
        to: String
      },
	  /* The runtime version of vue does not come with a compiler, so it does not support the template option in components. The compiler compiles template into the render function. The single-file component always uses the render template because the single-file template was compiled into the render function during the packaging process, which is called precompiling */
      render (h) { // This function takes an argument, usually called h, to help us create the virtual DOM. H is passed by vue
        return h('a', {	The h function takes three arguments: 1. Create a selector for this element. 2. Set some attributes for the tag. If they are DOM object attributes, add them to attrs. 3. A child element of the generated element, so it is an array
          attrs: {
            href: this.to
          },
		  on: {
			  click: this.clickHandler
		  }
        }, [this.$slots.default])
      },
	  methods: {
		  clickHandler (e) {
          // 1. Use pushState to change the browser address bar without sending a request to the server
          history.pushState({}, ' '.this.to)	// This method takes three arguments, 1.data. 2. Title Specifies the title of the page. 3. The url address
          this.$router.data.current = this.to
          e.preventDefault()
		  }
	  }
      // template: '<a :href="to"><slot></slot></a>'
    })
    const self = this
    Vue.component('router-view', {
      // Obtain the routing component corresponding to the current routing address
      render (h) {
        const component = self.routeMap[self.data.current]
        return h(component)
      }
    })
  }

  initEvent () {
	  window.addEventListener('popstate'.() = > {
		  this.data.current = window.location.pathname
	  })
  }
}

Copy the code