preface

This paper is only a demo, and there are many functions that have not been realized. Several articles will be published later to complete the core functions of vue-Router.

Core functions that have been implemented

  • router-linkandrouter-component
  • $routerand$route
  • hash modeandhistory mode
  • Switch components that listen for route changes

Then consider the functionality implemented

  • Embedded routines by
  • Dynamic Route Matching

thinking

When you install vue bucket via vue-CLI, you open SRC /router/index.js

import Vue from "vue";
import VueRouter from "vue-router";
import Home from ".. /views/Home.vue";

// Think 1: Why use vue.use
Vue.use(VueRouter);

const routes = [
  {
    path: "/".name: "Home".component: Home,
  },
  {
    path: "/about".name: "About".// route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () = >
      import(/* webpackChunkName: "about" */ ".. /views/About.vue"),},];// Reflection 2: Why new VueRouter
const router = new VueRouter({
  mode: "history".base: process.env.BASE_URL,
  routes,
});

export default router;
Copy the code

Drive the SRC/main points again. Js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

Vue.config.productionTip = false

// Think 3: Why is the root instance passed to the router
new Vue({
  router,
  store,
  render: h= > h(App)
}).$mount('#app')
Copy the code

It opens at the SRC/App. Vue

<template>
  <div id="app">
    <div id="nav">
      <! Router-view/router-link/router-view/router-view/router-view/router-view/router-view
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link>
    </div>
    <router-view/>
  </div>
</template>
<script>
export default {
    methods: {
      xx() {
        // Think 5: Why can I access the $router from any component and the $router is the same thing
        this.$router.push("/")
      }
  },
}
<script>
Copy the code

Thought 6: Why do I click router-link to switch to a view page but it doesn’t jump and refresh

My own thinking

  1. Use it is clear that VueRouter is a plug-in, and we need to implement an install method to install the plug-in
  2. New VueRouter({}) shows that VueRouter is a class, or constructor
  3. This is kept in suspense and will be answered later.
  4. Use (VueRouter) {import (VueRouter) {import (VueRouter) {import (VueRouter) {import (VueRouter) {import (VueRouter)}}
  5. Obviously, any component can access the same thing, indicating that the $router is mounted on vue. prototype.
  6. Router-link is essentially an A tag, but the default jump event is intercepted and instead switches the components rendered by the Router-View

The body of the

Create the mRouter. Js folder under SRC/Router to write our own VueRouter class.

1. The original look

1) First define the VueRouter class

2) Implement the install method, and register the two router-link and router-view components globally

3) Router-link and router-view these two things are arbitrary, I will improve them in the following

src/router/mRouter.js

class VueRouter {
  constructor() {}
}

VueRouter.install = function(vue) {
  vue.component("router-link", {
    render(h) {
      return h("a", {}, this.$slots.default); }}); vue.component("router-view", {
    render(h) {
      return h("div", {}, "renderrender"); }}); };export default VueRouter;
Copy the code

The effect

2. Mount $router

Q: As mentioned above, we want to mount $router on vue. prototype and we have a problem: The router is passed in new Vue({router}), and if vue.use (VueRouter) is executed before new Vue({}), then we cannot access the Vue instance in Install and therefore cannot mount it.

Faced with this problem, let’s think about what we expect. We want to defer execution to some point in the future, specifically when the root instance of new Vue({}) is created.

Implementation of the method we can look at the source code how to do

For each beforeCreate instance, determine whether it is the root instance for each beforeCreate instance. For each beforeCreate instance, the router attribute is displayed in the options for the root instance. For each beforeCreate instance, the router attribute is displayed for each beforeCreate instance.

Their implementation

VueRouter.install = function(vue) {
  vue.mixin({
    beforeCreate() {
      if (this.$options.router ! = =undefined) {
        vue.prototype.$router = this.$options.router; }}}); vue.component("router-link", {
    props: ["to"].render(h) {
      return h("a", { attrs: { href: this.to } }, this.$slots.default); }}); vue.component("router-view", {
    render(h) {
      // Print it
      console.log("test-router--".this.$router);
      return h("div", {}, "renderrender"); }}); };Copy the code

The effect

3. Create a response between URL and view

Here we need to use a variable current to record the current URL. When the URL changes, we update current as the view changes.

We have two modes: history and hash. We listen for popState and hashchange events respectively and update current when triggered.

The next question arises, if we change current directly, will the render function know that current is updated? Let’s try it out.

First we switch mode to hash mode because we haven’t done interception in history mode yet.

class VueRouter {
  constructor(options) {
    this.$options = options;

    this.current = "/";

    const strategy = {
      hash: () = > {
        window.addEventListener("hashchange".() = > {
          // '#/about/id=1123' => no # needed
          this.current = window.location.hash.slice(1);
        });
      },
      history: () = > {
        window.addEventListener("popstate".() = > {
          // / ABC is what we want
          this.current = window.location.pathname; }); }}; strategy[options.mode] && strategy[options.mode](); } } VueRouter.install =function(vue) {
  vue.mixin({
    beforeCreate() {
      if (this.$options.router ! = =undefined) {
        vue.prototype.$router = this.$options.router; }}}); vue.component("router-link", {
    props: ["to"].render(h) {
      let prefix = this.$router.$options.mode === "hash" ? "#" : "";
      return h("a", { attrs: { href: prefix + this.to } }, this.$slots.default); }}); vue.component("router-view", {
    render(h) {
      // Let's click on different urls to see if console fires
      console.log("render---".this.$router.current);
      return h("div", {}, "renderrender"); }}); };export default VueRouter;
Copy the code

Obviously the Render function has no idea that current has changed, so it only executes once.

Anyone who has ever used Vue knows how to handle this, we just need to make current responsive, and Vue will collect the dependency and trigger the corresponding Render function when current updates.

And here comes the question, how do we convert this into a responsive form. $set, object.defineProperty, new Vue({data():{return current:”/”}}) or something else?

$set = $set = $set = $set = $set = $set = $set = $set = $set = $set = $set = $set = $set Here, we don’t even have a reactive object, so how do we use $set?

The second way you can try to do it yourself, the third way is feasible, but we won’t use it here.

Var ue.util. DefineReactive is a utility function in Vue that helps us to create a reactive attribute.

/** * Define a reactive property on an Object. */
function defineReactive$$1 (obj, key, val, customSetter, shallow) {
  var dep = new Dep();

  var property = Object.getOwnPropertyDescriptor(obj, key);
  if (property && property.configurable === false) {
    return
  }

  // cater for pre-defined getter/setters
  var getter = property && property.get;
  var setter = property && property.set;
  if((! getter || setter) &&arguments.length === 2) {
    val = obj[key];
  }

  varchildOb = ! shallow && observe(val);Object.defineProperty(obj, key, {
    enumerable: true.configurable: true.get: function reactiveGetter () {
      var value = getter ? getter.call(obj) : val;
      if (Dep.target) {
        dep.depend();
        if (childOb) {
          childOb.dep.depend();
          if (Array.isArray(value)) { dependArray(value); }}}return value
    },
    set: function reactiveSetter (newVal) {
      var value = getter ? getter.call(obj) : val;
      /* eslint-disable no-self-compare */
      if(newVal === value || (newVal ! == newVal && value ! == value)) {return
      }
      /* eslint-enable no-self-compare */
      if(process.env.NODE_ENV ! = ='production' && customSetter) {
        customSetter();
      }
      // #7981: for accessor properties without setter
      if(getter && ! setter) {return }
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = !shallow && observe(newVal);
      dep.notify();
    }
  });
}
Copy the code

Let’s use this defineReactive to see if this works

let Vue = null;

class VueRouter {
  constructor(options) {
    this.$options = options;
    
    // The Vue here is obtained from install below
    Vue.util.defineReactive(this."current"."/");

    const strategy = {
      hash: () = > {
        window.addEventListener("hashchange".() = > {
          // '#/about/id=1123' => no # needed
          this.current = window.location.hash.slice(1);
        });
      },
      history: () = > {
        window.addEventListener("popstate".() = > {
          // '#/about/id=1123' => no # needed
          this.current = window.location.pathname; }); }}; strategy[options.mode] && strategy[options.mode](); } } VueRouter.install =function(vue) {
  Use () and then new VueRouter(), so we can access Vue from constructor
  Vue = vue;

  vue.mixin({
    beforeCreate() {
      if (this.$options.router ! = =undefined) {
        vue.prototype.$router = this.$options.router; }}}); vue.component("router-link", {
    props: ["to"].render(h) {
      let prefix = this.$router.$options.mode === "hash" ? "#" : "";
      return h("a", { attrs: { href: prefix + this.to } }, this.$slots.default); }}); vue.component("router-view", {
    render(h) {
      console.log("render---".this.$router.current);
      return h("div", {}, "renderrender"); }}); };export default VueRouter;
Copy the code

We’re just one step away from matching the path in the routing table with current and rendering the corresponding Component. I’m not considering nested routing and dynamic routing.

let Vue = null;

class VueRouter {
  constructor(options) {
    this.$options = options;

    Vue.util.defineReactive(this."current"."/");

    // this.current = "/";

    const strategy = {
      hash: () = > {
        window.addEventListener("hashchange".() = > {
          // '#/about/id=1123' => no # needed
          this.current = window.location.hash.slice(1);
        });
      },
      history: () = > {
        window.addEventListener("popstate".() = > {
          // '#/about/id=1123' => no # needed
          this.current = window.location.pathname; }); }}; strategy[options.mode] && strategy[options.mode](); } } VueRouter.install =function(vue) {
  Vue = vue;

  vue.mixin({
    beforeCreate() {
      if (this.$options.router ! = =undefined) {
        vue.prototype.$router = this.$options.router; }}}); vue.component("router-link", {
    props: ["to"].render(h) {
      let prefix = this.$router.$options.mode === "hash" ? "#" : "";
      return h("a", { attrs: { href: prefix + this.to } }, this.$slots.default); }}); vue.component("router-view", {
    render(h) {
      console.log("render---".this.$router.current);

      const component = this.$router.$options.routes.find((item) = > {
        return item.path === this.$router.current; })? .component ?? {render(h) {
          return h("div", {}, "404"); }};returnh(component); }}); };export default VueRouter;

Copy the code

conclusion

In fact, this part of the content of a lot of big estimate all know, the dish chicken is also recently began to see the source code, follow the big thinking go, and then put forward in accordance with the problem to think how to solve the problem step by step. Really can’t think of or gnaw the source code, after all, the standard answer is there.

reference

Follow along, you can also hand write the VueRouter

From routing to vue-router source code, take you through the front-end routing

Vue Router