Last time, we popularized the custom routing routes of the applet and started the routing journey; Today, I'll take advantage of the trend to apply routing on a single page and chat with you for 50 cents. If you don't talk well... Return... A dollar?
Single page application features
Suppose: in a web page, there is a button. Click it to jump to other pages in the station.
Multi page application: click the button to load a new html resource and refresh the whole page;
Single page application: click the button, there is no new html request, and only local refresh occurs, which can create a near native experience, as smooth as silk.
Why can SPA single page application have almost no refresh? Because its SP -- single page. When entering the application for the first time, the only html page and its public static resources are returned. The subsequent so-called "jump" will no longer take the html file from the server, but the DOM replacement operation is simulated.
So how does js catch the opportunity of component switching and change the browser url without refreshing? Rely on hash and HTML5 history.
hash routing
features
- Similar to www.xiaoming.com HTML #bar is a hash route. When # the following hash value changes, it will not request data from the server. You can monitor the change of URL through hashchange event, so as to carry out DOM operation to simulate page Jump
- No server cooperation is required
- Not friendly to SEO
principle
HTML5History routing
features
- History mode is a new function of HTML5. It is more intuitive than hash routing. It looks like this www.xiaoming.com HTML / bar, the simulated page Jump is through history Push state (state, title, URL) to update the browser route. When the route changes, listen to the pop state event to operate the DOM
- The back-end cooperation is required for redirection
- Relatively friendly to SEO
principle
Interpretation of Vue router source code
Take Vue router of Vue as an example. Let's talk about its source code.
Tips: because the focus of this article is to explain the two modes of single page routing, only some key codes are listed below, mainly:
- Register plug-ins
- Constructor of vueroter to distinguish routing modes
- Global registration component
- push and monitoring method of hash / HTML5History mode
- transitionTo method
Register plug-ins
First of all, as a plug-in, you should be conscious of exposing an install method and give it to Vue's father to use.
Install JS file defines the method of registering and installing the plug-in install, blends the method into the hook function of each component, and initializes the route when the beforeCreate hook is executed:
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 full text To represent the omitted method ... });
Distinguish mode
Then, we start from index JS find the base class VueRouter of the whole plug-in. It is not difficult to see that it adopts different routing instances according to different mode s in the constructor.
... import {install} from './install'; import {HashHistory} from './history/hash'; import {HTML5History} from './history/html5'; ... export default class VueRouter { static install: () => 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}`) } } } }
Register router link components globally
At this time, we may ask: when using Vue router, where are the common < router link / > and < router view / > introduced?
Go back to install JS file, which introduces and globally registers router view and router link components:
import View from './components/view'; import Link from './components/link'; ... Vue.component('RouterView', View); Vue.component('RouterLink', Link);
Yes/ components/link.js, the < router link / > component is bound with the click event by default. Click to trigger 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) } } };
As mentioned at the beginning, the vueroter constructor initializes History instances of different modes for different modes, so the router replace,router. The way of pushing is also different. Next, we pull the source code of these two modes respectively.
hash mode
History/hash. The HashHistory class is defined in the. JS file, which inherits from History / base JS.
Its prototype defines the push method: in the browser environment that supports HTML5 history mode (supportsPushState is true), call history Pushstate to change the browser address; In other browser environments, you will directly use location Hash = path to replace with a new hash address.
In fact, there are some questions here at the beginning. Since it is already a hash mode, why judge the supportsPushState? To support scrollBehavior ,history.pushState can pass the parameter key to the past, so that each url history has a key, and the key is used to save the location information of each route.
At the same time, the setupListeners method bound on the prototype is responsible for monitoring the timing of hash change: in the browser environment supporting HTML5 history mode, listen for the pop state event; In other browsers, it listens for hashchange. After listening to the change, trigger the handleRoutingEvent method and call the transitionTo jump logic of the parent class to replace the DOM.
import { pushState, replaceState, supportsPushState } from '../util/push-state' ... export class HashHistory extends History { setupListeners () { ... const handleRoutingEvent = () => { const current = this.current if (!ensureSlash()) { return } // The jump method under the parent class History called by transitionTo. After the jump, the path will be hash ed 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(() => { 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 ) } } ... // Process the URL with the incoming path in the form of hash function getUrl (path) { const href = window.location.href const i = href.indexOf('#') const base = i >= 0 ? href.slice(0, i) : href return `${base}#${path}` } ... // Replace hash function pushHash (path) { if (supportsPushState) { pushState(getUrl(path)) } else { window.location.hash = path } } // util/push-state. Methods in JS file export const supportsPushState = inBrowser && (function () { const ua = window.navigator.userAgent if ( (ua.indexOf('Android 2.') !== -1 || ua.indexOf('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' })()
HTML5History mode
Similarly, the HTML5History class is defined in history / HTML5 JS.
Define the push prototype method and call history Pushstate modifies the path of the browser.
At the same time, the prototype setupListeners method listens to the event of pop state and makes DOM replacement in time.
import {pushState, replaceState, supportsPushState} from '../util/push-state'; ... export class HTML5History extends History { setupListeners () { const handleRoutingEvent = () => { 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(() => { 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. Methods in JS file 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) } }
transitionTo handles routing change logic
The two routing modes mentioned above trigger this when listening Tooniti, what is this? It is actually defined in history / base The prototype method on the JS base class is used to handle the routing change logic.
First pass const route = this router. Match (location, this. Current) compares the incoming value with the current value and returns the corresponding routing object; Then judge whether the new route is the same as the current route, and return directly if it is the same; Different, then in this Execute the callback in confirmtransition to update the routing object and replace the DOM related to the view.
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) }) } } } ) } ... }
last
Well, the above is some small knowledge of single page routing. I hope we can get started and never give up together~~