1, the preface

This article introduces the implementation principle of VueRouter step by step. Before reading this article, you need to have a basic understanding of the use of vUE, and learn the basic concepts of class.

Implementation notes:

  • How to register a plug-in
  • Realize router-view and router-link components
  • How do I display components based on the current route
  • How to update components during route switching
  • How to implement nested routines

So with that in mind, let’s start implementing it step by step

Final code link

Making a link

2. Prepare test data

We can use VueCli to build a project for VueRouter. Here’s a brief description of commands

# If you use YARN
yarn global add @vue/cll
# If you use NPM
npm install -g @vue/cli
Create a Vue2 project
vue create vue-router-study
Copy the code

After the installation, start the service directly

cd vue-router-study
yarn serve
Copy the code

We can then run a test example using the official Vue-Router

yarn add vue-router
Copy the code

Write the file router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

export default new VueRouter({
  routes: [{path: '/'.component: () = > import('.. /components/HelloWorld.vue'),}, {path: '/a'.component: () = > import('.. /components/A.vue'),
      children: [{ path: '/a/b'.component: () = > import('.. /components/B.vue')}],},],})Copy the code

Edit main.js to add the router to the Vue option

import router from './router'

// ...

new Vue({
  router,
  render: h= > h(App),
}).$mount('#app')
Copy the code

Display our data in app.vue

<template>
  <div id="app">
    <div>
      <router-link to="/">Home page</router-link>
    </div>
    <div>
      <router-link to="/a">A page</router-link>
    </div>
    <div>
      <router-link to="/a/b">B page</router-link>
    </div>
    <router-view></router-view>
  </div>
</template>

// ...
Copy the code

Create two components a. vue and B.vue

components/A.vue

<template>
  <div>I'm component A<div>
      <router-view></router-view>
    </div>
  </div>
</template>

<script>
export default {
  name: "A"
};
</script>
Copy the code

componets/B.vue

<template>
  <div>I'm page B</div>
</template>

<script>
export default {
  name: "B"
};
</script>
Copy the code

Now go back to the page and see what it looks like

Now I’ll start implementing my own vue-Router plug-in

3. Implement plug-in registration

When we use VueRouter, we register with use, indicating that VueRouter is a plug-in. An install method needs to be implemented

Create a new file to implement our own VueRouter

Create a VueRouter class, write an Install method, and define a variable to hold Vue

src/avue-router.js

let Vue

class VueRouter {}

VueRouter.install = function(_Vue) {
  Vue = _Vue
  Vue.mixin({
    beforeCreate() {
      if (this.$options.router) Vue.prototype.$router = this.$options.router
    },
  })
}

export default VueRouter
Copy the code

Use our own avue-router.js on router/index.js

// import VueRouter from 'vue-router'
import VueRouter from '.. /avue-router'
Copy the code

Go back to the page and see if it displays properly. If successful, the plug-in is registered successfully

When vue.use (VueRouter), Vue automatically calls the install scheme. Using mixins, we can mount our router instance in the Vue option to the prototype and get the instance data from this.$router in the Vue instance

4. Implement router-link components

Mount the global component through Vue and concatenate props into href, filling in the values of the default slots

VueRouter.install = function(_Vue) {
  // ...
  
  Vue.component('router-link', {
    props: {
      to: {
        type: String.require: true,}},render(h) {
      return h(
        'a',
        {
          attrs: {
            href: The '#' + this.to,
          },
        },
        this.$slots.default
      )
    },
  })
}
Copy the code

Now back on the page, router-link is displayed normally.

5. Implement router-view component

You need to declare a reactive variable current to hold the current hash path. The router-view component matches the component in the Routes table based on this path and is displayed. And listens for hashchange to update the current path when the path is updated.

// ...

class VueRouter {
  constructor(options) {
    // Save instance configuration items
    this.$options = options
    Vue.util.defineReactive(this.'current'.window.location.hash.slice(1) | |'/')
    addEventListener('hashchange'.this.onHashChange.bind(this))
    addEventListener('load'.this.onHashChange.bind(this))}onHashChange() {
    this.current = window.location.hash.slice(1) | |'/'
  }
}

VueRouter.install = function(_Vue) {
  // ...
  
  Vue.component('router-view', {
    render(h) {
      let component = null
      const route = this.$router.$options.routes.filter(route= > route.path === this.$router.current)[0]
      if (route) component = route.component
      return h(component)
    },
  })
}
Copy the code

Since we are not implementing nested routines at the moment, we need to comment out the router-view in a. ue, otherwise it will cause an infinite loop

components/A.vue

<template>
  <div>I'm component A<div>
      <! -- <router-view></router-view> -->
    </div>
  </div>
</template>

<script>
export default {
  name: "A"
};
</script>
Copy the code

Now that we’re back on the page, we see that we can switch pages using router-link.

5.1 Implement nested routines by

Let’s look at the official notation

As can be seen, he defines a depth variable for each router-view component to determine its depth, and has a matched array, which records the corresponding route array of the current path.

So, for example, our hash address is/A /b, and matched should be

let matched = [
  {
    path: '/a'.component: () = > import('.. /components/A.vue'),
    children: [{ path: '/a/b'.component: () = > import('.. /components/B.vue')}],}, {path: '/a/b'.component: () = > import('.. /components/B.vue'),},]Copy the code

Now we implement the matched method in the VueRouter class. Through a recursive route table. Collect all route arrays for the current path.

class VueRouter {
  // ...
  
  match (routes) {
    routes = routes || this.$options.routes
    for (const route of routes) {
      // If it is the root directory, push a route into it and return it directly
      if (route.path === '/' && this.current === '/') {
        this.matched.push(route)
        return
      }
      // If it is not the root directory, determine all routes contained in the current directory and collect them
      if(route.path ! = ='/' && this.current.indexOf(route.path) ! = = -1) {
        this.matched.push(route)
        if (route.children) this.match(route.children)
      }
    }
  }
}
Copy the code

Next we’ll rewrite the Route-View component. Add routerView attributes to each Route-View component to determine whether it is a Router-View component. Starting with the current instance, loop up to figure out what level the current component is at. Save the value to the depth variable. Then select route of corresponding layer according to matched table to obtain component display of the table.

VueRouter.install = function (_Vue) {
  // ...
  
  Vue.component('router-view', {
    render (h) {
      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]
      if (route) component = route.component
      return h(component)
    }
  })
}
Copy the code

Now we can unpack the router-view comment in Components /A.vue. Display normal ~

End result