review

The core Principles of SPA Routing Trilogy, a practical Exercise of SPA Routing Trilogy, is finally coming to you. Before we start, let’s review what we learned in the previous article: Two popular modes for front-end routing are hash mode and History mode, which utilize the browser’s own features to implement single-page navigation, respectively.

  • Hash mode: window.location or a tag changes anchor values, window.hashchange() listens for anchor changes
  • History mode: history.pushState() and history.repalcestate () define the destination route, and window.onpopState () listens for URL changes caused by browser operations

That SPA routing trilogy practice exercise will bring what content, using the core principles learned before, plus the use of front-end routing in Vue technology stack, start to achieve their own front-end routing manager myRouter. Xiaobian will use native JS and Vue to implement hash mode routing and History mode routing respectively.

Native JS implements routing

Before implementing myRouter based on Vue technology stack, we will try out native JS.

Implementing Hash Routing

Before using native JS to implement hash routing, we need to first clarify the ways to trigger route navigation:

  • Using the A tag, trigger anchor changes and listen for window.onhashchange() for route navigation
  • JS dynamically changes the hash value, and window.location.hash is assigned to perform route navigation

Let’s go step by step and finish the HTML design first:

index.html

The < body > < div > < h3 > Hash pattern routing hop < / h3 > < ul class = "TAB" > <! - define routing - > < li > < a href = "# / home" > a TAB click jump home < / a > < / li > < li > < a href = "# / about" > a TAB click jump about < / a > < / li > < / ul > <! <div id="handleToCart"> JS dynamically click to jump to cart</div> <! <div id="routeViewHash" class="routeView"></div> </div>Copy the code

Next, define the action on the URL as the JSHashRouter class

Class JSHashRouter {constructor(RouterView){this.RouterView = RouterView} init(){ Redirect to #/ and render the corresponding UI if(! Location. The hash) {location. The hash = "# /"} else {this. RouterView. InnerHTML = 'current routing: '+ location. Hash} / / to monitor the hash value to change the window. The addEventListener (' hashchange' () = > {this. RouterView. InnerHTML = 'current routing: '+ location.hash }) } push(path){ window.location.hash = path } }Copy the code

The JSHashRouter class itself defines the routerView property, which receives the container for rendering the route UI. The JSHashRouter class defines two methods: init() and push()

  • init()

First, when the page is rendered, window.onhashChange () is not triggered and the routerView is rendered based on the current hash value. Listen for the window.onHashChange () event and re-render the routerView once the event is triggered.

  • push()

In the actual development process, the route hop needs to use JS dynamic Settings. Hash is set to window.location.hash to redirect routes. Note here that window.location.hash changing the hash value also raises the window.onHashchange () event.

So whether the a tag changes the hash or window.location.hash changes the hash, it rerenders the routerView in the window.onHashChange () event.

The call completes the hash route as soon as the JSHashRouter is instantiated:

index.html

<script type="text/javascript" src="./hash/hash.js"></script> <script> window.onload = function () { let routerview = Document.getelementbyid ('routeViewHash') // Let HashRouter = new JSHashRouter(RouterView) Var handleToCart = document.getelementById ('handleToCart'); handleToCart.addEventListener('click', function(){ hashRouter.push('/cart') }, false); } </script>Copy the code

Ok, take a look at the effect:

Implementing the History route

The hash route is successfully implemented. Before implementing the History route in the same way, specify the ways to trigger the jump of the History route:

  • Dynamic trigger pushState(), replaceState()
  • Intercepts the a tag default event, detects URL changes, and uses pushState() to jump.

The method of triggering route forward in History mode is slightly different from that in Hash mode. As we learned in the SPA Routing Trilogy, pushState(), replaceState(), and a tag URL changes do not trigger the window.onpopState () event. So what do we do? In real development, we need to jump in HTML. Fortunately, we can block the click event of tag A, block the default event of tag A, detect the URL, and use pushState() to jump.

index.html

The < div > < ul class = "TAB" > < li > < a href = "/ home" > click jump/home < / a > < / li > < li > < a href = "/ about" > click jump/about < / a > < / li > < / ul > <! -- JS dynamically changes the URL value, PushState redirect list</div> <div id="handleReplace" class=" BTN "> JS dynamic ReplaceState jump item</div> <! <div id="routeViewHistory" class="routeView"></div>Copy the code

The history route is similar to the HTML template defined by the hash route. The difference is that the href attribute of the A tag is defined differently. The URL in history mode does not contain the # sign. Next, it’s time to define the JSHistoryRouter class.

class JSHistoryRouter { constructor(routerview){ this.routerView = routerview } init(){ let that = this let linkList = document.querySelectorAll('a[href]') linkList.forEach(el => el.addEventListener('click', Function (e) {e.preventDefault() // prevents the <a> default jump event history.pushState(null, ", el.getAttribute('href')) // get the URL, Skip that. RouterView. InnerHTML = 'current routing: '+ location. The pathname})) / / to monitor the URL change window. AddEventListener (' popstate' () = > {this. RouterView. InnerHTML = 'current routing: '+ location. The pathname})} push (path) {history. PushState (null, ", path) enclosing routerView. InnerHTML =' current routing: '+ path} the replace (path) {history. ReplaceState (null, ", path) enclosing routerView. InnerHTML =' current routing: + path}}Copy the code

Like the JSHashRouter class, the JSHistoryRouter class itself defines the routerView property, which receives the container for rendering the route UI. The JSHistoryRouter class also defines three methods: init(), push(), and replace().

  • Init () does two main things:
    1. Define window.onpopState () to listen for history.go(), history.back(), and history.forword() events.
    2. Click the A TAB and the browser URL is updated by default to the href assigned URL, using e.preventDefault() to block the default event. PushState () updates the browser URL with the href attribute and rerenders the routerView.
  • The push() function rerenders the routerView by adding a browser URL via history.pushState().
  • The replace() function replaces the browser URL with history.replacEstate () and rerenders the routerView.

Ok, now just instantiate the JSHistoryRouter class and call the method to implement the history route! index.html

<script type="text/javascript" src="./history/history.js"></script> <script> window.onload = function () { let routerview = document.getElementById('routeViewHistory') let historyRouter = new JSHistoryRouter(routerview) // HistoryRouter instantiate historyRouter.init() // JS dynamically change URL value document.getelementById ('handlePush').addeventListener ('click', function(){ historyRouter.push('/list') }, false); document.getElementById('handleReplace').addEventListener('click', function(){ historyRouter.replace('/item') }, false); } </script>Copy the code

Here we go, here we go!

Careful students will have noticed that the first rendering of the page is handled in the Init () method of the HashRouter class, but not in the HistoryRouter class. Why is that? In Hash mode, the URL Hash value is changed. The browser request does not carry the Hash value, so the value ranges from http://localhost:8080/#/home to http://localhost:8080/#/cart. Browsers do not send requests. History mode, change is the other parts of the URL in addition to the anchor, so by http://localhost:8080/cart to http://localhost:8080/home, the browser will send the request again. This explains why the vue-router Hash mode does not require any processing on the back end, while the History mode back end redirects static resources that do not match the domain name to the same index.html page.

Well, using JS to achieve the basic route jump, is not more looking forward to Vue technology stack to achieve their own route, immediately arranged!

Source: github.com/yangxiaolu1…

Routing based on Vue

Vue-router and React-Router are popular single-page routing managers. There are some differences in the way they are used, but the core principles are pretty much the same, and once you’ve mastered one, the other is easy to understand. We refer to the use mode and function of VUe-Router to realize myRouter based on VUE technology stack. First of all, a simple VUE development environment was built through VUe-CLI. The building process is not described here. After adjustment, the directories are as follows:

├ ─ ─ the config ├ ─ ─ index. The HTML ├ ─ ─ package. The json ├ ─ ─ the SRC │ ├ ─ ─ App. Vue │ ├ ─ ─ the main, js │ ├ ─ ─ assets │ │ ├ ─ ─ CSS │ │ ├ ─ ─ image │ │ └ ─ ─ js │ ├ ─ ─ components │ ├ ─ ─ the plugins / / plug-in │ │ └ ─ ─ myRouter │ │ ├ ─ ─ index. The js / / myRouter │ │ └ ─ ─ the js / / Install MyRouter object function │ ├ ─ ─ the router │ │ └ ─ ─ MyRouter. Js │ ├ ─ ─ util │ └ ─ ─ the view │ └ ─ ─ home. Vue ├ ─ ─ the static └ ─ ─ vue.config.jsCopy the code

The nature of the Vue Router

Before implementing myRouter, let’s recall how the Vue Router was introduced in Vue.

  1. Install vue-router dependencies through NPM
    import VueRouter from vue-router
    Copy the code
  2. Define the routing variable Routes, and each route maps a component
    Const routes = [{path: '/foo', Component: {template: '<div>foo</div>'}}, // Import {path: '/bar', component: { template: '<div>bar</div>' } } ]Copy the code
  3. The router is instantiated with the routing variable routes as a configuration item
    Const router = new VueRouter({mode:'history', routes: routes})Copy the code
  4. Use (Router) allows each component in a Vue project to own router instances and current routing information
  5. Mount the Router on the root instance.
    export default new Vue({
        el: '#app',
        router
    })
    Copy the code

This.$router can be used in any component to get the router instance, or this.$route can be used to get the current route information. What information do we gain from configuring the route?

  • Router instances are created using new VueRouter({… }, that is, the VueRouter we introduced is actually the VueRouter class.
  • The router instance is an object. In other words, the Contractor of VueRouter receives an object parameter. Routes and mode are the attributes of this object.
  • Through the Vue. Use (…). Inject router instances for each component, and vue.use () actually executes the install method on the object.

Based on the three points, we can basically design the basic structure of MyRouter:

Class MyRouter {constructor (options) {this. Routes = options. Routes | | [] this. Mode = options. The mode | | 'hash' hash / / mode || history ...... } } MyRouter.install = function(Vue,options){ ...... } export default MyRouterCopy the code

Replace the Vue Router in the project with the defined MyRouter, run the project, the page blank, no error.

According to their own development habits, the basic structure of MyRouter is divided into two JS files install.js and index.js. The install.js file is used to implement the install method, and index.js is used to implement the MyRouter class.

Vue.use()

To better understand the implementation of Vue Router, take a look at what vue.use () can do when defining a plug-in. If you already know how to use vue.use (), skip this section.

If the plugin is an object, you must provide the install method. If the plug-in were a function, it would be used as the install method. When the install method is called, the Vue and the options passed in when the Vue is instantiated are passed in as arguments. Using the passed Vue, we can define some global variables, methods, components, etc.

  • Vue.com Ponent () Global component registration
    MyRouter. Install = function(Vue,options){Vue.component("my-router-view", {template: '<div> render router UI</div>'})}Copy the code

    Any component in the project can use <my-router-view> directly without import.

  • Vue.mixin() globally registers a mixin
    MyRouter.install = function(Vue,options){
        Vue.mixin({
            beforeCreate(){
                Object.defineProperty(this,'$location',{
                    get(){ return  window.location}
                })
            }
        })
    }
    Copy the code

With global mixin, it affects each vUE instance that is created separately afterwards. Add a new attribute $location to the injected vue instance via Object.defineProperty() and run the project. In each vue instance (component), you can get the Location Object via this.$location.

The global components, methods, filters, and logic for handling the injection of custom options defined in a custom plug-in are all done in the Install method.

$myRouter and $myRoute

When vue-Router is used in a project, two objects $router and $route are included in each vUE instance.

  • $router is an instantiation object of the Vue Router. It is a global object that contains all route objects.
  • $route is the current route object. Each route is a route object, which is a local object.

$myRouter

The final step when registering vue-Router is to mount the router on the root instance and instantiate the root component with the router as part of its parameters. Currently, only the root component has router instances, and none of the other child components has router instances. The question is, how do you put router instances into each component?

From our previous basic understanding of vue.use (), we know that vue.mixin () can register a mixin globally, which will be executed by every subsequent component. The options for mixin objects are “blended” into the options of the component itself, that is, the methods defined in vue.mixin () in install.js are merged with the methods in the component when each component is instantiated. Using this feature, you can pass router instances to each of the child components. Let’s look at the code:

install.js

MyRouter.install = function(Vue,options){ Vue.mixin({ beforeCreate(){ if (this.$options && this.$options.myRouter){ // This._myRouter = this.$options.myRouter; }else {this._myRouter= this.$parent && this.$parent._myRouter // add $Router to the current instance Object.defineProperty(this,'$myRouter',{ get(){ return this._myRouter } }) } }) }Copy the code

In the preceding code, the routing information is mixed in the component beforeCreate() stage, is there any special meaning? In the beforeCreate phase of Vue initialization, the parameters from the prototype method (vue.prototype), global API attributes, and global vue.mixin () defined by Vue will be merged into a new options and mounted on $options. On the root component, $options gets the myRouter object. This refers to the current Vue instance, the root instance, to which the myRouter object is mounted via the custom _myRouter variable. In the beforeCreate initialization phase, in addition to merging vue.mixin () defined in the install method, the child component merges parameters of the parent component, such as props defined by the parent component on the child component, Also included, of course, is the _myRouter attribute that you define on the root component.

Have you ever wondered why you can take the _myRouter object of the parent component when you know it is a child? To explain this, let’s consider the order in which the parent and child components execute their life cycles:

Parent beforeCreate -> parent created -> parent beforeMount -> child beforeCreate -> child create -> child beforeMount -> child Mounted -> parent Mounted

When the child component beforeCreate executes, the parent component beforeCreate has finished executing, and _myRouter has been mounted on the parent component instance.

Finally, we mount $myRouter to the component instance via Object.defineProperty.

$myRoute

$myRoute is used to store the current path. $myRoute = path, name, hash, meta, fullPath, query, params; $MyRouter = MyRouter; $MyRouter = MyRouter; $MyRouter = MyRouter; $MyRouter = MyRouter $MyRouter = $MyRouter = $MyRouter = $MyRouter = $MyRouter = $MyRouter = $MyRouter = $MyRouter

install.js

MyRouter.install = function(Vue,options){ Vue.mixin({ beforeCreate(){ ...... DefineProperty (this,'$myRoute',{get(){return this._myrouter.current}})}})}Copy the code

The assignment of the current attribute is explained in the MyRouter class implementation.

MyRouterLink and MyRouterView components

Back to where we were, we implemented the basic structure of the MyRouter class by reviewing the introduction of the Vue Router. Next, let’s look at how the Vue Router is used.

The Vue Router defines two components: <router-link> and <router-view>


  • is used for routing navigation and is rendered as an A tag by default, passing in the to attribute to define the destination route

  • is used to render matched routing components
<div id="app">
  <p>
    <router-link to="/foo">Go to Foo</router-link>
    <router-link to="/bar">Go to Bar</router-link>
  </p>
  <router-view></router-view>
</div>
Copy the code

Similar to Vue Router, add two global components <my-router-view> and <my-router-link> to myRouter. The <my-router-link> component is defined by the <my-router-link> component.

<router-link> will be rendered as a tag by default, <router-link to=”/foo”>Go to foo </router-link> Will render the final result in the browser <a href=”/foo”>Go to foo </a>. Of course, the tag type can be specified through the tag prop of <router-link>. <my-router-link> <solt/> <solt/> <my-router-link> <solt/> <solt/> <solt/>

<template>
  <a :href="to"><slot/></a>
</template>
<script>
export default {
  name: 'MyRouterLink',
  props:['to'],
  data () {return {}}
}
</script>
Copy the code

How do we define Tag Prop? What if the trigger condition for routing navigation is not Click, but mouseover? Remember the Render function provided in Vue, which combines data to generate a Virtual DOM tree, Diff algorithm, and Patch to generate new components. <my-router-link> uses the render function definition.

link.js

Export Default {name: 'MyRouterLink', functional: true, props: {to: {// Target navigation Type: [String, Object], required: Type: String, default: 'a'}, event: {// Trigger event type: String, default: 'click'}}, render: (createElement, {props,parent}) => { let toRoute = parent.$myRoute.mode == 'hash'? `#/${props.to}`:`/${props.to}` return createElement(props.tag,{ attrs: { href: toRoute }, on:{ [event]:function(){} } },context.children) } };Copy the code

Ok, reference the component to see the effect!

App.vue

<div class="footer"> <my-router-link to="home" tag="p"> </my-router-link> <my-router-link To ='classify'> </my-router-link> </div>Copy the code



Render normally, no errors reported, render successfully using the P tag, but problems soon followed. <my-router-link> is rendered as a tag by default. In history routing mode, the default jump event of a tag will not only jump out of the current Document, but also cannot trigger the popState event. If you’re using a custom tag, then you need to update the URL with pushState or window.location.hash when triggering navigation. No, why not block the default event for tag A, so that tag A is No different from custom tags, navigation tags are routed via pushState or window.location.hash.

link.js

export default { ...... render: (createElement, {props,parent,children}) => { let toRoute = parent.$myRoute.mode == 'hash'? '#/${props. To} ':' /${props. To} 'let on = {'click':guardEvent} on[props. Event] = e=>{guardEvent(e Push ()} return createElement(props. Tag,{attrs: {href: toRoute }, on, },children) } }; function guardEvent(e){ if (e.preventDefault) { e.preventDefault() } }Copy the code

It’s not easy. Countless brain cells died just implementing the MyRouterLink component. Next, give your brain a break! MyRouterView component is implemented according to MyRouterLink component. The function of MyRouterView is relatively simple, just render the component that matches the current route. $myRoute: $myRoute: $myRoute: $myRoute: $myRoute: $myRoute: $myRoute: $myRoute: $myRoute In fact, in $route matched array, also recorded component information, including nested by component, dynamic route matching regX and so on, small series just simplified it.

view.js

export default {
    ......
    render: (createElement, {props, children, parent, data}) => {
        let temp = parent.$myRoute && parent.$myRoute.component?parent.$myRoute.component.default:''
        return createElement(temp)
    }
}

Copy the code

install.js

import MyRouterView from './view'
MyRouter.install = function(Vue,options){
    Vue.component(MyRouterView.name, MyRouterView)
}
Copy the code

Now that it’s time to get excited, add <my-router-view> and see if it works!

App.vue

<template> <div id="app"> <div class="footer"> <my-router-link to="home" tag="p"> <my-router-link to='classify'> </my-router-link> <my-router-link to='cart'> </my-router-link> </div> </div> </template>Copy the code

The MyRouter plugin is already in prototype form. It’s not easy! Get ready, here’s the kicker

Core classes MyRouter

It’s time to complete the MyRouter class. Before we complete the MyRouter class, let’s determine what needs to be done.

  1. Options, array type routing table, and mode representing the current routing mode
  2. When vue-router is used, not only path but also name can be used to match routes. The routes in the routing table cannot be easily matched. Therefore, the routing table is converted to the key:value format
  3. In order to distinguish the two modes of routing, create a HashRouter class, HistoryRouter class, collectively called Router class, add the history attribute to store the instantiated Router class
  4. Define the current attribute, which stores the target routing information, and mount it to each instance via $myRoute
  5. Define push(), replace(), and go() methods

MyRouter class does a lot of things, as long as the logic is clear, no amount of fear. Let’s do it bit by bit. Let’s start by putting together the basic structure of the MyRouter class.

index.js

Class MyRouter {constructor (options) {this. Routes = options. Routes | | [] / / the routing table. This mode = options. The mode | | 'hash' / / Model hash | | history enclosing routesMap = Utils. The createMap (enclosing routes) / / routing tables loaded with key: value form this. History = null / / storage instantiation HashRouter or HistoryRouter this.current= {name: ", meta: {}, path: "/", Hash: ", Query :{}, params: {}, fullPath: ', Component :null} // Record the current route // Instantiate the HashRouter, HistoryRouter class switch (options.mode) {case 'hash': this.history = new HashRouter(this) case 'history': this.history = new HistoryRouter(this) default: this.history = new HashRouter(this) } } init(){ this.history.init() } push(params){ this.history.push(params) } replace(params){ this.history.replace(params) } go(n){ this.history.go(n) } } export default MyRouterCopy the code

When the MyRouter class is initialized, we instantiate the Router class in the history property. Using the MyRouter init(), push(), replace(), and go() methods, You’re calling a HashRouter or HistoryRouter class method. So you need to include these four methods in both the HashRouter class and the HistoryRouter class.

Utils.createMap(routes)

CreateMap (routes){let nameMap = {} // Create a key value object with the name of each route. Let pathMap = {} // Create a key value object with the path of each route routes.forEach((route)=>{ let record = { path:route.path || '/', component:route.component, meta:route.meta || {}, name:route.name || '' } if(route.name){ nameMap[route.name] = record } if(route.path){ pathMap[route.path] = record } })  return { nameMap, pathMap } }Copy the code

Recombine each routing object in the routing table routes, respectively adjust to route.path, route.name as the keyword, value as the record object. This is just a simple process and does not consider nested routines, parent component routing, redirection, props, etc.

Let’s go back and look at the five things that the MyRouter class needs to do. All we need to do now is define the HashRouter class and the HistoryRouter class and assign current to them. Without further ado, turn it on!

HashRouter class

As the name suggests, the HashRouter class is used to redirect routes in Hash mode. By defining the MyRouter class, it is clear that the HashRouter class needs to define four functions. At the same time, the HashRouter class assigns the current attribute of the MyRouter class and needs to receive the parameters of the MyRouter class. So you have the basic framework for the HashRouter class.

hash.js

Export default class HashRouter {constructor(router){constructor(router){this.router = router} init(){} push(params){} replace(params){} go(n){} }Copy the code

The key to implement hash redirect is to monitor changes in the HASH value of urls. Remember the original JS implementation of Hash routing, the principle is exactly the same, the code can be directly used.

export default class HashRouter { ...... Init (){this.createroute () // when the page first loads, Determine the current route window.addEventListener('hashchange', This. HandleHashChange. Bind (this)) / / monitoring hashchange} handleHashChange () {enclosing createRoute ()} / / update the current routing current CreateRoute () {let the path = location. Hash. Slice (1) let the route = this. The router. RoutesMap. The pathMap/path / / update the current routing this.router.current = { name: route.name || '', meta: route.meta || {}, path: route.path || '/', hash: route.hash || '', query:location.query || {}, params: location.params || {}, fullPath: location.href, component: route.component } } ...... }Copy the code

The HashRouter class is implemented in the same way as the JSHashRouter class, with a slight difference in how the UI is rendered. The HashRouter class renders components via <my-router-view>.

CreateRoute () in the HashRouter class obtains the HASH value of the URL, that is, the path value. Through the routesmap. pathMap object in the MyRouter class, obtains the route configuration options matched by the current route. Merge the route configuration options into the myRouter.current object, which is mounted to each instance via $myRoute. In other words, $myroute.componentis the routing component we matched, and <my-router-view> is the component to render.

Code to achieve here, should be able to achieve the basic jump, take a look at the effect, verify!



The initial rendering of the page is fine, and clicking the bottom navigation can jump to the page normally, but why is the page not updated? Although myrouter. current associates hash changes with <my-router-view>, it does not bind myrouter. current bidirectionally. <my-router-view> does not know that myrouter-current has changed. Is it necessary to implement bidirectional binding? Of course not! The Vue hidden API (vue.util.definereActive ()) is used to define the response properties of an object. That should be easy. When MyRouter is initialized, use vue.util.definereActive () to make myRouter.current listen.

install.js

MyRouter.install = function(Vue,options){ Vue.mixin({ beforeCreate(){ ...... Object.defineProperty(this,'$myRoute',{ .... }) // The new code uses Vue defineReactive to listen for changes to the current route vue.util.definereactive (this._myrouter,'current')}})}Copy the code

Now let’s see if the contents of <my-router-view> are updated.



It wasn’t easy. We finally got what we wanted. The HashRouter push(), replace(), and go() methods are copied directly from the JSHashRouter code.

export default class HashRouter { ...... push(params){ window.location.href = this.getUrl(params) } replace(params){ window.location.replace(this.getUrl(params)) } go(n){window.history.go(n)} // get the current URL getUrl(path){let path = "if(utils.getProperty (params) == 'string'){path  = params } else if(params.name || params.path){ path = params.name?params.name:params.path } const fullPath = window.location.href const pos = fullPath.indexOf('#') const p = pos > 0?fullPath.slice(0,pos):fullPath return `${p}#/${path}` } }Copy the code

Referring to vue-Router, the dynamic navigation method can be a string or an object describing an address, and the getUrl function handles various cases of params.

For HistoryRouter class, you can use HashRouter as a HashRouter class. For HistoryRouter class, you can use HashRouter as a class.

CccHistoryRouter class * *

The HistoryRouter class, like the Hash class, requires push(), replace(), and go() to dynamically set the navigation, and a private argument receives the MyRouter class. The only difference is the way you handle listening for URL changes.

history.js

export default class HistoryRouter { constructor(router){ this.router = router } init(){ Window.addeventlistener (' popState ', ()=>{// Route change})} push(params){} replace(params){} go(n){}}Copy the code

History routing mode navigation is implemented by history.pushState(), history.replacestate () and window.onpopState (). As with the HashRouter class, the implementation of the HistoryRouter class can still take the code directly from the native JS implementation’s JSHistoryRouter class.

history.js

Export default class HistoryRouter {constructor(router){this.router = router} init(){// Listen for popState window.addEventListener('popstate', ()=>{this.createroute (this.getLocation()) // Navigation UI render})} push(params){history.pushState(null, '', Path) this.createRoute(params) // Navigation UI render} replace(params){history.replacestate (null, "", Params.path) this.createroute (params) // Navigation UI render} go(n){window.history.go(n)} getLocation () {let path = decodeURI(window.location.pathname) return (path || '/') + window.location.search + window.location.hash } }Copy the code

Since history.pushState and history.replaceState changes to the browser’s history state do not trigger popState, only the browser’s back and forward keys trigger popState events. So when you modify history with history.pushState, history.replaceState, and listen for popState, you need to perform navigation UI rendering based on the current URL. In addition to UI rendering, it is important to note that <my-router-view> will not update router-current until it is updated.

history.js

export default class HistoryRouter { ...... createRoute(params){ let route = {} if(params.name){ route = this.router.routesMap.nameMap[params.name] }else{ let path = Utils. GetProperty (params) = = 'String'? Params: params. Path route = this. The router. RoutesMap. PathMap [path]} / / routing update this.router.current = { name: route.name || '', meta: route.meta || {}, path: route.path || '/', hash: route.hash || '', query:location.query || {}, params: location.params || {}, fullPath: location.href, component: route.component } } }Copy the code

In the native JS JSHistoryRouter class, we modified the a tag click event to block the default event and use pushState to implement route navigation. Why wasn’t this logic added to the HistoryRouter class? Remember the implementation of <my-router-link>, which blocks the default event of the A tag and uses the push method of the MyRouter class for route navigation.

The HistoryRouter class has completed its basic functionality, and I can’t wait to verify the code!



Perfect realization, a little admire yourself! I had the illusion that I could write vue-Router. Compared with vue-Router, there are still many functions that are not implemented, such as nested routing, route navigation guard, transition animation, etc. MyRouter plug-in only implements simple route navigation and page rendering. MyRouter has made a good start, and I believe that the functionality will not be a problem after completion. Interested partners can continue to develop with xiaobian!

Source: github.com/yangxiaolu1…

conclusion

Vue-router is not difficult to achieve the idea, the source logic is quite complex, MyRouter compared to vue-Router although simplified a lot, but the overall idea is the same. Xiaobian believe that MyRouter implementation will be able to help partners more quickly master vue-Router source code. Do you have the same experience as xiaobian when writing code, you need to constantly modify and improve the previous code, even if the thought was very comprehensive, but still personally delete the code, just the <my-router-link> component implementation, xiaobian modified more than five times. The process was painful, but the result was rewarding and full of accomplishment. I really admire the developers of vue-Router. I don’t know how many times they had to modify it to get to where they are now!

Welcome to the final article in the SPA Routing Trilogy. In this article, xiaobi will take you into the world of vue-Router source code. I believe that after understanding the vue-Router implementation ideas, we can really realize their own front-end routing.