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:
- Change the URL without causing the page to refresh
- 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:
- The first time the page is added, if there is no hash value, then you need to redirect to #/
- The first time the page loads, the hashChange event is not triggered, so you need to manually assign the routeView(UI)
- 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:
- After the page is loaded, if the URL does not contain pathname, you need to redirect to the root path /
- 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
- Define popState event. When the event is triggered, update routeView(UI).
Conclusion:
- 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
- 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:
- You first receive a plugin constructor, plugin
- Check whether the passed plug-in exists in Vue’s global plugins list (_intalledPlugins)
- 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
- Execute install or an executable function, passing in the args argument list
Summary:
- Vue maintains a global list of installedPlugins to prevent them from being registered more than once
- 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:
- Pass Vue as a parameter for internal use in the plugin (you don’t want to pack Vue when you’re packaging it!)
- 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
- Router._route = router._route = router._route = router._route = router._route = router._route = router._route = router._route Router, this.router, this.router, this.route)
- 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:
- Save the Vue instance app on the router instance
- Select * from location where location is located
- Obtain the current route from location
- Save the current route (history.current = route)
- [root@vue] [root@vue] [root@vue] [root@vue] [root@vue] [root@vue] [root@vue] [root@vue] [root@vue] [root@vue] [root@vue
- 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…