1. Web routing

1.1 Back-end Routes

The concept of Web routing is simply to render different pages based on different urls. In the era when the front and back ends are not separated, routing often refers to the back-end routing (server routing), that is, when the server receives the HTTP request from the client, it reads files and databases according to the requested URL, and uses the template engine to combine the corresponding results with the template for rendering. Send the rendered page to the client.

The advantages and disadvantages

  • Advantages: SEO friendly, the page crawler crawls is the final render page.
  • Disadvantages: every time to initiate a request to refresh the page, user experience is not good, server pressure.

1.2 Front-end Routing

When it comes to front-end routing, Ajax and SPA must be mentioned first. The rise of Ajax technology promotes the emergence of SPa-single page application. Because Ajax can achieve partial update of the page, the interaction of the single page application page and the jump of the page are not refreshed. No refresh means that there is no need to deal with the request of THE HTML file, so the user experience is very good. However, since the page data needs to be obtained through Ajax, the HTML obtained by the crawler is only the template rather than the final rendered page, which is not conducive to SEO. For single-page applications, front-end routing is required. The concept of front-end routing is simply that, when the routing changes, it does not request the server, but changes the DOM (component replacement) through JS, and sends Ajax to obtain data to achieve the effect of page hopping. So there are two key points to implement front-end routing:

  • How to change the URL so that the browser doesn’t send requests to the server.
  • How to listen for URL changes and perform corresponding actions

There are two routing modes for implementing front-end routing: hash mode and History mode

2. Implementation mode of front-end routing

2.1 the hash pattern

concept

A hash is the hash after the URL and what follows it

The characteristics of

The Hash mode has the following characteristics

  • Changing the hash value does not cause the browser to send a request to the server and does not cause a page refresh.
  • The hashChange event is triggered when the hash value changes.
  • Hash changes are recorded in the browser’s history, and you can go back to the previous hash by using the browser’s back button.
  • The hash is never submitted to the server, even if the page is refreshed.

It can be seen that the hash mode can fully meet the implementation requirements of front-end routing. Therefore, before the emergence of H5 history mode, the hash mode was basically used to realize front-end routing.

The advantages and disadvantages

Advantages:

  • 1, good compatibility, support low version and IE browser.
  • 2. Front-end routing does not require the support of the server.

Disadvantages:

  • URL with #, path ugly

2.2 the history mode

concept

Before HTML5, browsers already had a history object to control the jump to the page history, mainly through the following methods.

History.back () : back history.go(n) : loads a specific page in the history listCopy the code

In the HTML5 specification, History has the following new apis: pushState (append) and replaceState (replace), which can change urls without sending requests, as well as popState events. Using these apis, you can implement front-end routing in a different way, similar to the hash mode implementation, but with HTML5 implementation, the url of a single page application does not have an extra #, which is more beautiful.

There are two things to say about the History mode:

  • How does history mode listen for route changes

In history mode, browser forward and backward (history.back(), history.forward(), etc.) will trigger the popState event, but pushState, replaceState will not trigger the PopState event. Therefore, in order to implement the interception of route changes, we need to rewrite these two methods. We can add event notification through EventBus.

  • The history mode requires back-end support

Since history mode does not have a # sign, the browser will still send a request to the server when the user manually refreshes or enters the application directly from a URL. But the server does not recognize this URL, so to avoid this, the history pattern requires the server to redirect all routes that do not match to the root page.

The advantages and disadvantages

Advantages:

  • Path to the good-looking

Disadvantages:

  • 1, poor compatibility, not compatible with IE9.
  • 2. Server support is required.

3. Realize the vue – the router

After introducing the concept of front-end routing and its implementation mode, we will try to implement vue-Router plug-in, including vue-Router class, two global components: router-link, router-view and install method.

3.1 Implement the Router class

We use Hash mode to implement this, so the core point of vue-Router is to add event listening for hashchange and load events, and then pull the corresponding routing components from the routing table based on the current URL in the callback and provide them to router-View rendering. So the first question is: How do I get components out of the routing table based on the URL?

A basic idea is that we simply grab the current hash value when we listen for a URL change, and then traverse the routing table to find the component of the option whose path is the current hash value. The problem with this is that nested routing cannot be handled. If nested routing is configured in the routing table, the child routes cannot be matched using hash values alone. To solve this problem, we can use a matched array to store each level of components in the matching process from parent to child, so that each level of router-view components can only be rendered as needed.

How can router-View update in response to URL changes? We can take advantage of the vUE responsive data feature here. We know that the data in data in a single file component is reactive, and when the data is updated, all the places that use the data are updated in response. In this case, the router-view component will obviously use the matched array, so we just need to change the matched data into responsive data. The vue.util. defineReactive API defines the response attributes of an object as follows:

Vue.util.definereactive (obj,key,value,fn) obj: target object,key: target object attribute; Value: attribute valuesCopy the code

We use it to define matched as a responsive attribute of the router instance, so that when matched changes, the router-view will also render responsively. Also note that this method requires a VUE instance. How do I get the VUE instance? This can be retrieved and saved in the vue-router install method, which will be explained later. Next, we will implement the Router class.

Let vue class myRouter{constructor (options){this.$options = options // Save the current hash value, That match this path. The current = window. The location. The hash. Slice (1) | | '/' / / to initial value / / save matching routing information at all levels in the process of Vue. Util. DefineReactive (this, 'matched', []) // match method can recursively traverse the routing table, obtain the matching relationship this.match() // add the listening event, the event callback uses this, So bind the context window.addeventListener ('hashchange', this.onhashchange.bind (this)) window.adDeventListener ('load', This. OnHashChange. Bind (this))} onHashChange () {/ / update the matching path. This current = window. The location. The hash. Slice (1) this. Matched = [] this. The match ()} / * * * @ description traverse the routing table, save the matching relation between * / match (routes) {/ / traverse total routing table by default routes routes of = | | this.$options.routes; for (let i = 0; i < routes.length; i++) { const route = routes[i]; If (route.path === "/" && this.current === "/") {this.matched. Push (route); break; // The current route contained in the URL is pushed into the matched array and its sub-routes are recursively traversed. == "/" && this.current.includes(route.path)) { this.matched.push(route); if (route.children) { this.match(route.children); } break; }}}}Copy the code

3.2 Implement two global components

Vue-router has two global components:

  • Router-link Indicates the router-link
  • Router-view Indicates the route placeholder

Let’s implement it separately

router-link

Router-link is used to redirect routes. The implementation of router-link is relatively simple because it is essentially a tag. Therefore, it only needs to render an A tag to implement router-link. Note, however, that since this is a runtime environment and template compilation is not possible, the template syntax cannot be used, so we can use the Render function instead.

The herf attribute corresponds to the router-link to attribute. The tag content is what the user wrote to the router-view. We can fetch it from slots (this.$slots) and add it to the actual A tag.

export default {
  props: {
    to: {
      type: String,
      required: true
    }
  },
  render (h) {
    return h('a', { attrs: { href: '#' + this.to } }, this.$slots.default)
  }
}
Copy the code

router-view

Router-view is used to render routing components. The router implemented previously has added the processing of routing matching relation, it will store the matching relation of all levels in the matched array according to the current URL. How can router-View render according to the matched array?

In fact, for a nested routine, each level of route has a router-view corresponding to it, that is, router-view must also be nested. Therefore, router-view only needs to know its own level, specifically, the number of items in the matched array. To achieve this, we can add a marker variable and a depth count variable to each router-view. The Router-View determines whether its parent node has this marker. If it does, it indicates that it is a child route. In this way, each router-view will finally get its own level. According to this level, it only needs to get the corresponding routing component from the Matched array and render it. Use the render function instead of using template syntax.

Export default {render(h) {router-view this.$vNode.data. RouterView = true; // let depth = 0; let parent = this.$parent; While (parent) {const vnodeData = parent.$vnode && parent.$vnode.data; if (vnodeData && vnodeData.routerView) { depth++; $parent = parent.$parent; } let component = null; Const route = this.$router. Matched [depth]; Component if (route) {component = route.component.component.component.route.component.component.route; } return h(component); }};Copy the code

3.3 Implement the install method

Vue-router is a vUE plug-in, the implementation principle of which was mentioned earlier. It exposes a install method that blends the beforeCreate life cycle with a global mixin (vue.mixin) that causes the beforeCreate hooks for all components to trigger the behavior. We mount the router instance to the VUE prototype in beforeCreate, making it easy to call the router directly from the VUE prototype anywhere. How do you do this?

When we use vue-router, we create a vue root instance in main.js and import and mount the router option. This means that only the vue root instance has router. Therefore, the beforeCreate hook will determine whether the current component has the Router option. If it does, the vuE-Router root instance will be mounted to the vUE prototype.

When implementing the Router class earlier, we said we need to save the vue instance in the install method. Why do we do this? The reason the vue plug-in exposes an install method is because the install method is called when we register the component with the vue.use() method and we pass the VUE as a parameter, so we can save the vue instance in the Install method.

In addition, the install method registers the two global components implemented earlier. The following concrete implementation is based on the above ideas:

Myrouter. install = function (_Vue){// Save vue instance vue = _Vue vue. Mixin ({beforeCreate ()){// Ensure that the root instance is executed. Because only the root instance has the router option. Prototype.$router = this.$options. Router}}) {vue.prototype ('router ', 'vue.prototype ', $router = this. Link) Vue.component('router-view', View) }Copy the code

At this point, hack version of vue-router based on hash mode is complete. The level is limited, welcome correction 😁.

Reference: juejin. Cn/post / 684490… Juejin. Cn/post / 685457…