VueRouter-router系列组件编码

462 阅读2分钟

VueRouter

本章我们要书写initComponents这个方法,这个方法我们要实现两个组件router-linkrouter-view

router-link 组件实现

 /**
   * 
   * @param {*} vue 的实例
   */
  initComponents(vue) {
    vue.component('router-link', {
      props: {
        to: String
      },
      template: '<a :href="to"><slot></slot></a>'
    })
  }

这么一看代码好像很简单哦!

没有太对的代码去做实现,我们就添加了一个a标签和一个插槽,没有其他的标记,但是我们该什么进行注册呢?又该怎么做呢?

我们要进行注册的时候,就需要执行initComponentscreateRouteMap这两个方法,我们可以在 install的时候调用这两个方法

 _vue.mixin({
      beforeCreate() {
        if (this.$options.router) {
          _vue.prototype.$router = this.$options.router;
          this.$options.router.init();
        }
      },
    });

然后我们可以运行npm run serve进行测试啦!

但是。。。

image.png 不仅没有渲染出来,还有一堆报错,无奈改改吧!

image.png 该怎么解决呢?? 下面我使用以下两个方法进行维护

vue.config.js

我们进入vue-cli官网中查看runtimeCompiler(运行时编译器)的配置

image.png

使用完整版的vue进行维护

// vue.config.js

/**
 * @type {import('@vue/cli-service').ProjectOptions}
 */
module.exports = {
  // 选项...
  runtimeCompiler: true,
};

image.png ok这样就显示出来了

render版本进行维护

initComponents(vue) {
    vue.component("router-link", {
      props: {
        to: String,
      },
      // template: '<a :href="to"><slot></slot></a>',
      render(h) {
        return h(
          "a",
          {
            attrs: {
              href: this.to,
            },
          },
          [this.$slots.default]
        );
      },
    });
  }

重新运行一边

image.png

router-view

这个组件相等于一个占位符,我们需要工具路由地址,并将它渲染到对应的位置来

vue.component("router-view", {
      render(h) {
        // 我们需要先找到当前的路由地址 self.data.current
        // 找到对应路由所对应的组件
        const component = self.routeMap[self.data.current];
        // h函数可以将我们的路由转换成虚拟dom传递给页面
        return h(component);
      },
    });

这样页面是可以正常渲染了,但是还需要一些的优化

优化

我们利用popstate的方法进行页面回退的监听,然后对datacurrent进行修改

 initEvent() {
    window.addEventListener("popstate", () => {
      this.data.current = window.location.pathname;
    });
  }

完整代码

let _vue = null;

export default class Router {
  $options = null;
  routeMap = null;
  data = null;
  // Vue.use 方法会调用对应组件的 install 方法
  static install(Vue) {
    // 1. 判断插件是否被安装
    if (Router.install.installed) {
      return;
    }
    Router.install.installed = true;
    // 2. 将`install`方法中的参数`vue`插入到全局中
    _vue = Vue;
    // 3. 将创建`vue`实例时候传入的`Router`对象注入到`vue`实例上
    // 使用混入
    _vue.mixin({
      beforeCreate() {
        if (this.$options.router) {
          _vue.prototype.$router = this.$options.router;
          this.$options.router.init();
        }
      },
    });
  }

  constructor(options) {
    // 记录构造函数中传入的选项
    this.options = options;
    // 将来将options的规则解析到 routeMap中来,键值对的形式
    this.routeMap = {};
    // data是响应式的对象,使用vue的提供的方法,方便之后路由更新我们可以及时更新页面
    this.data = _vue.observable({
      current: "/",
    });
  }
  /**
   * @description 注册初始化方法
   */
  init() {
    this.createRouteMap();
    this.initComponents(_vue);
    this.initEvent();
  }
  /**
   * @description  遍历路由规则,把路由规则解析成键值对的形式,储存到routeMap中
   */
  createRouteMap() {
    this.options.routes.forEach((route) => {
      this.routeMap[route.path] = route.component;
    });
  }
  /**
   *
   * @param {*} vue 的实例
   */
  initComponents(vue) {
    const self = this;
    vue.component("router-link", {
      props: {
        to: String,
      },
      // template: '<a :href="to"><slot></slot></a>',
      render(h) {
        return h(
          "a",
          {
            attrs: {
              href: this.to,
            },
            on: {
              click: this.clickHander,
            },
          },
          [this.$slots.default]
        );
      },
      methods: {
        clickHander(e) {
          history.pushState({}, "", this.to);
          this.$router.data.current = this.to;
          e.preventDefault();
        },
      },
    });
    vue.component("router-view", {
      render(h) {
        // 我们需要先找到当前的路由地址 self.data.current
        // 找到对应路由所对应的组件
        const component = self.routeMap[self.data.current];
        // h函数可以将我们的路由转换成虚拟dom传递给页面
        return h(component);
      },
    });
  }
  initEvent() {
    window.addEventListener("popstate", () => {
      this.data.current = window.location.pathname;
    });
  }
}

到这里我们就结束了模拟路由的history模式,其核心就是理由vue的插件的对应回调还有render的方法进行的一些操作,不过我这也是站在前人的肩膀上做的一些小总结。