Last time, I popularised the custom routes of the small program and started the route journey. Today, homemomentum on the single page application routing, with everyone Lao a fifty cents, if Lao is not good… Back… A dollar?

Single page application features

“Suppose:” In a Web page, there is a button that can be clicked to jump to another page on the site.

“Multi-page application:” Click the button to reload an HTML resource and refresh the whole page.

“Single page app:” with the click of a button, no new HTML requests are made and only partial refreshes occur, creating a near-native experience that is silky smooth.

Why can SPA single page application have almost no refresh? Because of its SP — single-page. The first time you enter the application, you return a unique HTML page and its public static resources. Subsequent “jumps” no longer take an HTML file from the server. They are just DOM replacement operations, zhuang.

So how does JS catch the timing of a component switch without refreshing the browser URL? With hash and HTML5History.

Hash routing

Characteristics of the

  1. similarwww.xiaoming.html#barHash routing, when#When the subsequent hash value changes, the data is not requested from the server and can be passedhashchangeEvent to listen for changes to the URLDOMAction to simulate a page jump
  2. No server cooperation is required
  3. Not SEO friendly

The principle of

hash

HTML5History routing

Characteristics of the

  1. HistoryPatterns are a new feature of HTML5 that is more intuitive than hash routing and looks something like thiswww.xiaoming.html/barThe simulated page jump is throughhistory.pushState(state, title, url)To update the browser route and listen when the route changespopstateEvent to operateDOM
  2. The backend is required for redirection
  3. Relatively SEO friendly

The principle of

HTML5History

Vue-router source code interpretation

Take Vue routing Vue -router as an example, we together to polish its source code.

Since the focus of this article is on the two modes of single-page routing, here are just some of the key code:

  1. To register the plugin
  2. VueRouter’s constructor to distinguish routing modes
  3. Global Registry component
  4. Push and listen methods for hash/HTML5History mode
  5. TransitionTo method

To register the plugin

First, as a plug-in, be conscious of exposing an install method to Vue dads to use.

The install. Js file defines the method install for registering the install plug-in, mixing methods for each component’s hook function, and initializing the route when the beforeCreate hook executes:

Vue.mixin({
  beforeCreate () {
    if (isDef(this.$options.router)) {
      this._routerRoot = this
      this._router = this.$options.router
 this._router.init(this)  Vue.util.defineReactive(this.'_route'.this._router.history.current)  } else {  this._routerRoot = (this.$parent && this.$parent._routerRoot) || this  }  registerInstance(this.this)  },  // In the text... To represent the method of ellipsis .}); Copy the code

To distinguish the mode

From index.js, we find the plugin’s base class VueRouter, which constructor uses different routing instances for different modes.

.import {install} from './install';
import {HashHistory} from './history/hash';
import {HTML5History} from './history/html5';
.export default class VueRouter {  static install: (a)= > void;  constructor (options: RouterOptions = {}) {  if (this.fallback) {  mode = 'hash'  }  if(! inBrowser) { mode = 'abstract'  }  this.mode = mode   switch (mode) {  case 'history':  this.history = new HTML5History(this, options.base)  break  case 'hash':  this.history = new HashHistory(this, options.base, this.fallback)  break  case 'abstract':  this.history = new AbstractHistory(this, options.base)  break  default:  if(process.env.NODE_ENV ! = ='production') {  assert(false.`invalid mode: ${mode}`)  }  }  } } Copy the code

Register router-link components globally

Where did the
and
come from when using vue-router?

Back in the install.js file, it imports and registers router-view and router-link components globally:

import View from './components/view';
import Link from './components/link';
.Vue.component('RouterView', View);
Vue.component('RouterLink', Link);
Copy the code

In./components/link.js, the
component is bound to the click event by default, which triggers the handler method to perform the corresponding routing operation.

const handler = e= > {
  if (guardEvent(e)) {
    if (this.replace) {
      router.replace(location, noop)
    } else {
 router.push(location, noop)  }  } }; Copy the code

As mentioned earlier, the VueRouter constructor initializes History instances for different modes, so router.replace and router.push are different. Next, let’s take a look at the source code for these two modes.

Hash pattern

In the history/hash.js file, we define the HashHistory class, which inherits from the history/base.js history base class.

Its prototype defines the push method: in an HTML5History browser environment (supportsPushState is true), call history.pushState to change the browser address. In other browsers, location.hash = path is used to replace the new hash address.

SupportsPushState = supportsPushState = supportsPushState = supportsPushState To support scrollBehavior, history.pushState can be passed with a key, so that each URL history has a key, which holds the location of each route.

At the same time, the setupListeners method on the prototype listens for the timing of hash changes: on browsers that support HTML5History, it listens for popState events; In other browsers, they listen for hashchange. When the change is detected, the handleRoutingEvent method is triggered to invoke the parent class transitionTo jump logic to perform DOM replacement operations.

import { pushState, replaceState, supportsPushState } from '.. /util/push-state'
.export class HashHistory extends History {
  setupListeners () {
. const handleRoutingEvent = (a)= > {  const current = this.current  if(! ensureSlash()) { return  }  // transitionTo is the jump method in the History parent of the transitionTo call, after which the path is hashed  this.transitionTo(getHash(), route => {  if (supportsScroll) {  handleScroll(this.router, route, current, true)  }  if(! supportsPushState) { replaceHash(route.fullPath)  }  })  }  const eventType = supportsPushState ? 'popstate' : 'hashchange'  window.addEventListener(  eventType,  handleRoutingEvent  )  this.listeners.push((a)= > {  window.removeEventListener(eventType, handleRoutingEvent)  })  }  push (location: RawLocation, onComplete? :Function, onAbort? :Function) {  const { current: fromRoute } = this  this.transitionTo(  location,  route => {  pushHash(route.fullPath)  handleScroll(this.router, route, fromRoute, false)  onComplete && onComplete(route)  },  onAbort  )  } } . // Handle the URL with a hash path passed in function getUrl (path) {  const href = window.location.href  const i = href.indexOf(The '#')  const base = i >= 0 ? href.slice(0, i) : href  return `${base}#${path}` } . / / replace the hash function pushHash (path) {  if (supportsPushState) {  pushState(getUrl(path))  } else {  window.location.hash = path  } }  // util/push-state.js export const supportsPushState =  inBrowser &&  (function () {  const ua = window.navigator.userAgent   if (  (ua.indexOf('Android 2.')! = =- 1 || ua.indexOf('the Android 4.0')! = =- 1) &&  ua.indexOf('Mobile Safari')! = =- 1 &&  ua.indexOf('Chrome') = = =- 1 &&  ua.indexOf('Windows Phone') = = =- 1  ) {  return false  }  return window.history && typeof window.history.pushState === 'function' }) ()Copy the code

HTML5History mode

Similarly, the HTML5History class is defined in history/html5.js.

Define the push prototype method and call history.pusheState to change the path of the browser.

At the same time, the prototype setupListeners method listens for popState events and DOM replacements are made when appropriate.

import {pushState, replaceState, supportsPushState} from '.. /util/push-state';
.export class HTML5History extends History {

  setupListeners () {
  const handleRoutingEvent = (a)= > {  const current = this.current;  const location = getLocation(this.base);  if (this.current === START && location === this._startLocation) {  return  }   this.transitionTo(location, route => {  if (supportsScroll) {  handleScroll(router, route, current, true)  }  })  }  window.addEventListener('popstate', handleRoutingEvent)  this.listeners.push((a)= > {  window.removeEventListener('popstate', handleRoutingEvent)  })  } push (location: RawLocation, onComplete? :Function, onAbort? :Function) {  const { current: fromRoute } = this  this.transitionTo(location, route => {  pushState(cleanPath(this.base + route.fullPath))  handleScroll(this.router, route, fromRoute, false)  onComplete && onComplete(route)  }, onAbort)  } }  . // util/push-state.js export function pushState (url? : string, replace? : boolean) {  saveScrollPosition()  const history = window.history  try {  if (replace) {  const stateCopy = extend({}, history.state)  stateCopy.key = getStateKey()  history.replaceState(stateCopy, ' ', url)  } else {  history.pushState({ key: setStateKey(genStateKey()) }, ' ', url)  }  } catch (e) {  window.location[replace ? 'replace' : 'assign'](url)  } } Copy the code

TransitionTo Handles route change logic

The two routing modes mentioned above both trigger this.transitionto when listening. What is this? It is a prototype method defined on the history/base.js base class to handle the change logic of the route. Const route = this.router. Match (location, this.current) const route = this.router. Match (location, this.current) Then check whether the new route is the same as the current route. If so, return directly. If not, a callback is performed in this.confirmTransition to update the routing object and replace the view-dependent DOM.

export class History {
. transitionTo (
    location: RawLocation,
onComplete? :Function.onAbort? :Function  ) {  const route = this.router.match(location, this.current)  this.confirmTransition(  route, () = > { const prev = this.current  this.updateRoute(route)  onComplete && onComplete(route)  this.ensureURL()  this.router.afterHooks.forEach(hook= > {  hook && hook(route, prev)  })   if (!this.ready) {  this.ready = true  this.readyCbs.forEach(cb= > {  cb(route)  })  }  },  err => {  if (onAbort) {  onAbort(err)  }  if (err && !this.ready) {  this.ready = true  // https://github.com/vuejs/vue-router/issues/3225  if(! isRouterError(err, NavigationFailureType.redirected)) { this.readyErrorCbs.forEach(cb= > {  cb(err)  })  } else {  this.readyCbs.forEach(cb= > {  cb(route)  })  }  }  }  )  } .} Copy the code

The last

Well, the above is a single page routing some small knowledge, I hope we can together from the beginning to never give up ~~