I. How to implement front-end routing

1. What is front-end routing?

URL changes cause UI updates (without refreshing the page)

So two questions to think about:

  1. Change the URL without causing the page to refresh
  2. How do I detect URL changes

2. Implementation scheme

Hash implementation

The hash is the part of the URL after the #, and changing the hash value does not cause the page to refresh or resend the request to the server. You can listen for it to change with a HashChange event. You can change the hash value in the following three ways:

  • The browser changes the URL backwards and forwards
  • Change the URL with the A tag
  • Change the URL using window.location.hash

Ps: The hashChang event can be triggered in any of the above three ways

The history to achieve

History is a new addition to HTML5 that provides two ways to modify the browser’s history, neither of which will cause a page refresh

  • PushState to add a history to the browser and modify the address bar
  • ReplaceState, replace the current history in the browser, and modify the address bar

History provides a popState listener event, but it will only be triggered in the following two cases

  • Click the browser forward and back buttons
  • Show the back, go, and forward methods for calling history

Ps: pushState and replaceState do not trigger popState events

Second, the native way of implementation

Hash implementation

<! DOCTYPE html> <html lang="en"> <body> <ul> <ul> <! - define routing - > < li > < a href = "# / home" > home < / a > < / li > < li > < a href = "# / about" > about < / a > < / li > <! </div> </ul> </ul> <script> Let routerView = Document.querySelector ('#routeView') window.addEventListener('DOMContentLoaded', ()=>{ if(! Location.hash){// If there is no hash value, then redirect to #/ location.hash="/"}else{// If there is a hash value, then render the corresponding UI let hash= location.hash; routerView.innerHTML = hash } }) window.addEventListener('hashchange', ()=>{ let hash = location.hash; routerView.innerHTML = hash console.log('hashChange') }) </script> </body> </html>Copy the code

Code parsing:

  1. The first time the page is added, if there is no hash value, then you need to redirect to #/
  2. The first time the page loads, the hashChange event is not triggered, so you need to manually assign the routeView(UI)
  3. When the HashChange event is triggered (i.e. clicking the A link, manually changing the hash value, etc.) two things need to be done
    • Change the value of location.hash
    • Assignment to routeView (UI)

The history to achieve

<! DOCTYPE html> <html lang="en"> <body> <div> <ul> <! - define routing - > < li > < a href = "/ home" > home < / a > < / li > < li > < a href = "/ about" > about < / a > < / li > <! </div> </div> </div> </div> <script> Let routerView = Document.querySelector ('#routeView')  window.addEventListener('popstate', ()=>{ let pathName = location.pathname; routerView.innerHTML = pathName console.log('popstate'); }) window.addEventListener('DOMContentLoaded', load, false) function load (e) { ! Location.pathname && (location.pathName ="/") // If there is no pathname value, So redirect to/let ul = document.querySelector('ul') ul.addeventListener ('click', function (e) { e.preventDefault() if (e.target.nodeName === 'A') { let src = e.target.getAttribute('href') PushState (SRC, null, SRC) // Change the address in the URL routerView.innerhtml = SRC // Update the UI}}, false) } </script> </body> </html>Copy the code

Code parsing:

  1. After the page is loaded, if the URL does not contain pathname, you need to redirect to the root path /
  2. Bind the click event to the A link and block the default event for the A tag. When the event executes, change the address in the URL and update the UI
  3. Define popState event. When the event is triggered, update routeView(UI).

Conclusion:

  1. The first time a page loads, you need two things:
    • We need to determine whether the hash | pathname in the URL has a value. If it is empty, we need to assign a value (/) to them.
    • Since the first load does not trigger the HashChange | PopState event, you need to manually update the UI
  2. After the event is triggered, you need to change the value in the URL (assign values to location.hash and history.pushState) and update the UI

3. Vue-router implementation

Only the hash mode section is implemented here, and the history section is quite different, so you can supplement it yourself. Before implementing vue-Router, let’s take a look at what the plug-in mechanism in Vue looks like. How does it register?

Use () registers the plugin globally

Paste the vue.use code first

Use = function(plugin) {// maintain a plugin list globally, To prevent multiple registrations of the same plug-in const installedPlugins = this. _installedPlugins | | the if (this. _intalledPlugins = []) (installedplugin.indexof (plugin) > -1) return this const args = toArray(Argumens, 1) If (typeof plugin.install === 'function') {typeof plugin.install === 'function') { plugin.install.applay(plugin, args) } else if(typeof plugin === 'function') { plugin.apply(null, Function toArray (list, function toArray (list, function toArray)); start) { start = start || 0 let i = list.length - start const ret = new Array(i) while (i--) { console.log('i=', i) ret[i] = list[i + start] } return ret }Copy the code

Code parsing:

  1. You first receive a plugin constructor, plugin
  2. Check whether the passed plug-in exists in Vue’s global plugins list (_intalledPlugins)
  3. Splicing the argument list args, intercepting the arguments of the use function, starting with the first bit of the subscript (the arguments passed when the plug-in was registered), and then inserting the Vue constructor unshit into the first bit of the array
  4. Execute install or an executable function, passing in the args argument list

Summary:

  1. Vue maintains a global list of installedPlugins to prevent them from being registered more than once
  2. Vue’s plug-in must be either an object with a install method, or an executable function that executes as if it were a install method. You can also install a method or executable function that takes two parameters, which are:
    • The first argument is the constructor that takes Vue
    • The second argument is an optional object (passed when the plug-in is registered)

Install method vuerouter. install

// eslint-disable-next-line no-unused-vars let _Vue export function install (Vue) { _Vue = Vue Vue.mixin({ beforeCreate If (this.$options && this.$options. Router) {// () {if (this.$options && this.$options _routerRoot = this this._router = this.$options.router this._router.init(this) // Call the init method of the router instance Vue.util.defineReactive(this, '_route', This._routerroot = (this.$parent && this.$parent-._routerRoot) this._routerroot = (this.$parent && this.$parent. | | this}}}) / / 3, enclosing $router - > enclosing _routerRoot. The router enclosing $route - > enclosing _routerRoot. The router. _route Object.defineProperty(Vue.prototype, '$router', { get () { return this._routerRoot._router } }) Object.defineProperty(Vue.prototype, '$route', {get () {return this._routerroot._route}}) // 4, router-view router-link and router-link Vue.component('router-link', { props: { to: String }, render (h) { var mode = this._routerRoot._router.mode let to = mode === 'hash' ? '#' + this.to : this.to return h('a', { attrs: { href: to } }, this.$slots.default) } }) Vue.component('router-view', { render (h) { var component = this._routerRoot._route.component return h(component) } }) }Copy the code

Code parsing:

  1. Pass Vue as a parameter for internal use in the plugin (you don’t want to pack Vue when you’re packaging it!)
  2. There are a few things you can do with vue.mixin:
    • Attach the router to the root component
    • Make each VUE instance have a _routerRoot pointing to the root component instance
    • The response_route attribute is defined via vue.util.definereactive (). By changing the _route on the Vue instance, the render() method of the Vue instance is automatically called and the RouteView component content is updated
    • Call the init method on the router instance, passing in the Vue instance as a parameter
  3. Router._route = router._route = router._route = router._route = router._route = router._route = router._route = router._route Router, this.router, this.router, this.route)
  4. Router-link and router-view components

VueRouter class implementation

Now that we have implemented the VueRouter. Install method and called init method on the instance of VueRouter, let’s implement a VueRouter class

Constructor

Constructors do several things:

  • Receive the options parameter and save mode and options.routes on the instance
  • Map the route list to the key-value format for future use
  • According to the type of mode (HashHistory | HTML5History | AbstractHistory) to instantiate a history (I’m here to write a hash pattern)

Init method implementation

Implementation ideas:

  1. Save the Vue instance app on the router instance
  2. Select * from location where location is located
  3. Obtain the current route from location
  4. Save the current route (history.current = route)
  5. [root@vue] [root@vue] [root@vue] [root@vue] [root@vue] [root@vue] [root@vue] [root@vue] [root@vue] [root@vue] [root@vue
  6. Set the HashChange route listening event and re-execute steps 2 — 5 when the route changes

Ps: If you look at VueRouter’s source code, you will find that its push and replace methods also call the transitionTo method in history, which implements step 2 and step 5

Execute the process

  • Save the Vue instance
  • Call history.transitionto (step 2 — Step 6 above)
  • Execute history.listen(cb) (Ps: cb is a callback function that changes the _route value of the Vue instance)
// Init method in history.listen(route => {this.app._route = route})..... // History class listen (cb) {this.cb = cb}Copy the code

The specific implementation is as follows:

import { install } from './install.js' import History from './history' class VueRouter { constructor (options) { this.app = null this.mode = options.mode || 'hash' this.routes = options.routes this.history = new History(options.routes)} init (app) {this.app = app var History = this.history // ! location.pathname && (location.pathname = '/') // window.addEventListener('popstate', e => { // let path = location.pathname // this.history.transitionTo( // this.history.getCurrentRoute(path), // route => this.history.updateroute (route) //) //}) history.transitionto (// To use vue instance render history. 2) to make a hash event listeners getCurrentLocation (), (route) => { history.setupListener(route) } ) history.listen(route => { this.app._route = route }) } push (location) { this.history.push(location) } replace (location) { this.history.replace(location) } } VueRouter.install = install export  default VueRouterCopy the code

Implementation of the History class

class History { constructor (routes) { this.current = null this.cb = null this.routerMap = this.createRouterMap(routes) This.ensurehash () // Determines if the URL contains #/, and if it does not, resets the URL} ensureHash() {! location.hash && (location.hash = '/') } transitionTo (location, onComplete) { let route = this.routerMap[location] this.updateRoute(route) onComplete && onComplete(route) } updateRoute  (route) { this.current = route this.cb && this.cb(route) } getCurrentRoute (location) { return this.routerMap[location]  } createRouterMap (routes = []) { return routes.reduce((module, route) => { module[route.path] = route return module }, {}) } setupListener (route) { window.addEventListener('hashchange', e => { this.transitionTo( this.getCurrentLocation() ) }) } getCurrentLocation () { var href = window.location.href var index = href.indexOf('#') return index > -1 ? href.slice(index + 1) : '/' } push (location) { this.transitionTo( location, () => {// change hash pushHash(location)})} replace (location) {this.transitionto (location, () => {replaceHash(location)})} Listen (cb) {this.cb = cb}} export default History function PushHash (hash) {location.hash = hash} // Replace the hash after the URL function replaceHash (hash) {var href = window.location.href var index = href.indexOf('#') var base = index > -1 ? href.slice(0, index) : href window.location.replace(base + '#' + hash) }Copy the code

Four, reference

  • Juejin. Cn/post / 685457…
  • Juejin. Cn/post / 684490…