In my opinion, learning a framework, the best way to quickly master its underlying principle is to implement a simple version of the same function, so we will learn the underlying principle of VUe-Router today, and try to write a simple version of vue-Router.

This is how we normally use routing

This is when we think

  1. vue-routerWhat does this module do internally?
  2. Vue.use(VueRouter)What do you do?

Here are the answers:

Vue-router implements an install method, because vue-router is also a plug-in. If you want to register vue.use (), you need an install method. Vue-router implements and declares two global components (router-view and router-link), as well as several other instance methods, such as $router.push(). Vue.use() calls the install method in the incoming plug-in to unregister vue-Router.Copy the code

Next, we will implement vue-Router based on the answer.

In order to make you better understand, this article is more code and comment combination, after all, this thing is not easy to explain clearly in the way of text, code is the most realistic.

Create a constructor

First, create the VueRouter constructor

// router/vue-router.js

let Vue; // Used to save Vue instances
class VueRouter {
  constructor(options) {
    // this.$options refers to the VueRouter instance and is passed in when the value is new
    this.$options = options; }}Copy the code

Here this.$options is the route set passed in to the VueRouter instance with a value of new

Create the install method

The install method is created so that vue.use () calls this method, deregisters vue-Router, and implements related functionality

// router/vue-router.js

Use calls this method to register VueRouter, where the parameter _Vue was passed in when vuue. Use was called, i.e., the Vue instance
VueRouter.install = function(_Vue) {
  Vue = _Vue;  // Save the Vue instance globally for easy use elsewhere

  // Mount the $router attribute to the Vue prototype
  mountRouter();

  // Implement the router-link component
  mountRouterLink();

  // Implement the router-view component
  mountRouterView();
};
Copy the code

Use (vueRouter). We save it globally so that other places can easily use the Vue instance. Remember, this is only initialization, so we can’t get the Router instance yet.

Next we implement the following three methods in Install:

  1. Mount the $Router attribute to the Vue prototype (mountRouter)
  2. Implementing the router-link component (mountRouterLink)
  3. Implement router-view component (mountRouterView)

1. Mount the $router attribute to the Vue prototype

Why mount the $Router attribute? This is because when we render the route through the router-View component, we use the information in the router object to render the content of the route page for different routing addresses.

Mount the $router attribute to the Vue prototype as follows:

// router/vue-router.js

function mountRouter() {
  // Global mixin purpose: delay the following logic until the router is created and appended to the option
  Vue.mixin({
    // This hook is called every time a component creates an instance, so it is executed multiple times
    beforeCreate() {
      // 1. The router attribute is available only when the root instance is executed, because this option is available only in the root instance
      $option = router; $option = router; $option = router
      if (this.$options.router) {        
        // This.$options refers to the new Vue instance in main.js
        // We mount it to the previously declared global Vue variable so that other places can use the information on the Router instance
        Vue.prototype.$router = this.$options.router; }}}); }Copy the code

When vue.use () registered vue-router, we could not obtain the complete router instance on the Vue, so we used global mixing to delay obtaining the router instance on the new Vue instance in main.js. As long as we execute the new Vue() step in main.js, our vue-router is already mounted and we can get the router instance. At this point we can print this.$option to verify

2. Implement the router-link component

We usually use router-link to jump between routes. The underlying implementation of router-link is actually the A tag, but it jumps through the form of anchor point (#). In fact, the page does not change, but it just renders different routing pages to achieve the jump effect.

// router/vue-router.js

function mountRouterLink() {
  // Register the global component router-link
  Vue.component("router-link", {
    props: {
      
       
      to: {
        type: String.required: true}},render(h) {
      // The render function in vue is used to render, so why not use the template template
      // Since there is no compiler here, we know that in order to use templates in Vue, we must have a compiler
      // However, in addition to using the render function, we can also use the JSX method
      / / : return < a href = {' # '+ enclosing the to} > {this. $slots. The default} < / a >
      return h(
        "a", {
          attrs: {
            href: "#" + this.to
          }
        },
        this.$slots.default ); }}); }Copy the code

Let’s try using our own router-link component on the page to see if it works as expected

We can see that we actually change the routing address by clicking on different routes. So the question is, how to click on different routes and render the corresponding route page content? It’s time to implement the most important router-view component.

3. Implement the router-view component

We all know that router-View is used to render different routing pages based on the routing address. The implementation is as follows

// router/vue-router.js

function mountRouterView() {
  // Register the global component router-view
  Vue.component("router-view", {
    // Use the render function to render
    render(h) {
    
      // Name of the route currently located
      let component = null;
      
      // Compare the currently located route name on VueRouter's instance
      const route = this.$router.$options.routes.find(
        $router. Current Indicates the name of the currently located route
        route= > route.path === this.$router.current
      );
      
      // If the currently located route is in the previously declared routes list
      if (route) {
        component = route.component;
      }
      
      // The corresponding component page is returned and rendered to the page by the render function
      returnh(component); }}); }Copy the code

So here’s the problem: How do I get the name of the route I’m currently locating? Can we listen for changes in the URL address bar, and then intercept the relevant routing field, can we get the currently located route?

That’s right, let’s do the listening in the VueRouter constructor

// router/vue-router.js

let Vue; // Used to save Vue instances
class VueRouter {
  constructor(options) {
    // this.$options refers to the VueRouter instance and is passed in when the value is new
    this.$options = options;
    
    // Listen for hash changes
    window.addEventListener("hashchange".() = > {
      // current indicates the changed route name
      let current = window.location.hash.slice(1); }); }}Copy the code

However, the problem comes again. Although I have obtained the changed route name, how can I dynamically render the corresponding route page content while listening to the route change? If you call the mountRouterView method, you will create a router-view component each time. We know that there can only be one router-view component globally, which is obviously not feasible.

We add the route name variable to the bidirectional binding feature. When the value changes, the render function in our mountRouterView method can dynamically render. The router instance will be rerendered whenever it listens for any changes to the router instance.

We continue to implement this in the VueRouter constructor

// router/vue-router.js

let Vue; // Used to save Vue instances
class VueRouter {
  constructor(options) {
    // this.$options refers to the VueRouter instance and is passed in when the value is new
    this.$options = options;
    
    // Set current as response data, indicating the name of the current route
    // The router-view render function can be executed again in the future
    const initial = window.location.hash.slice(1) | |"/";
    // Add bidirectional binding to a variable
    Vue.util.defineReactive(this."current", initial);
    
    // Listen for hash changes
    window.addEventListener("hashchange".() = > {
      // When the current value is changed, the current on the $router instance will also change
      // Make the router-view render function run again
      this.current = window.location.hash.slice(1); }); }}Copy the code

As soon as the current property of the bidirectional binding feature changes, the router-View will re-render the new routing page content to achieve the effect of page hopping. Let’s try it out

In this way, we have basically implemented the function of vue-Router. Isn’t it amazing that the underlying principle of vue-Router is so simple? Of course, more routing instance methods here I did not go to implement, interested students, you can try to achieve it.

Vue router is a router that can be used to make a router. It is a router that can be used to make a router.