原理

hash 原理

<!DOCTYPE html>
<html lang="en">
<body>
  <button id="myBtn">按钮</button>
  <script>
    const btn = document.getElementById('myBtn');
    window.addEventListener('DOMContentLoaded', () => {
      console.log(location.hash);
    });
    btn.addEventListener('click', () => {
      location.hash = '#/lance';
    });
    window.addEventListener('hashchange', () => {
      console.log(location.hash);
    })
  </script>
</body>
</html>

history 原理

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <button id="myBtn">按钮</button>
  <script>
    const btn = document.getElementById('myBtn');
    window.addEventListener('DOMContentLoaded', () => {
      console.log(location.pathname);
    });
    btn.addEventListener('click', () => {
      const state = { name: 'user' };
      history.pushState(state, '', 'user');
      // state, title, url
    });
    window.addEventListener('popstate', () => {
      console.log(location.pathname);
    });
  </script>
</body>
</html>

实现

步骤

  1. class VueRouter
  2. 添加静态方法 install()Vue.use() 时需要调用
    1. Vue在调用 install 时,会把 Vue 构造函数传给我们
    2. Vue.mixin -> beforeCreate 中:
      1. 寻找根组件(存在 this.$options.router,也就是 new VueRouter 实例);找到后调用 init 方法开始执行
  3. init 方法中要调用
    1. 监听 hash 值变化(window.addEventListener => DOMContentLoadedhashchange
    2. 创建 路由映射表 (routes数组 => hashMap{ path: route })
    3. 注册全局组件 router-linkrouter-view

my-router/index.js

let Vue;

class VueRouter {
  constructor(options) {
    this.$options = options;
    this.routeMap = {};
    this.vm = new Vue({
      data() {
        return {
          currentPath: '/'
        }
      }
    });
  }
  init() {
    // 1. 监听hash值变化
    this.bindEvent();
    // 2. 创建路由映射表
    this.createHashMap();
    // console.log(this.routeMap);
    // {/: {path: '/', name: 'Home', component: {…}}, /about: {path: '/about', name: 'About', component: {...}}

    // 3. 注册全局路由组件 router-link、router-view
    this.initRouteComponent();
  }
  bindEvent() {
    // 页面加载完监听
    window.addEventListener('DOMContentLoaded', this.handleHashChange.bind(this), false);
    // hash值变化后监听
    window.addEventListener('hashchange', this.handleHashChange.bind(this), false);
  }
  handleHashChange() {
    console.log('handleHashChange-hash:', window.location.hash);
    const hash = this.getHashValue();
    // vm实例下的属性发生改变,会导致重新渲染
    this.vm.currentPath = hash;
  }
  // 获取hash值
  getHashValue() {
    // console.log("getHashValue-hash:", window.location.hash.slice(1));
    return window.location.hash.slice(1) || '/';
  }
  // 创建路由映射表
  createHashMap() {
    // 把 routes数组 转 hash 方式存储
    this.$options.routes.forEach(item => {
      this.routeMap[item.path] = item;
    });
  }
  // 初始化全局路由组件
  initRouteComponent() {
    Vue.component('router-view', {
      render: h => {
        const component = this.routeMap[this.vm.currentPath].component;
        // return h('h1', 'Hello JS++');
        return h(component);
      }
    });
    Vue.component('router-link', {
      props: {
        to: String,
      },
      render(h) {
        // h函数: 标签名, 属性集合
        // this.$slots.default: 拿到插槽内容
        return h('a', {
          attrs: {
            href: '#' + this.to
          }
        }, this.$slots.default);
      }
    });
  }

  // install 得是个静态方法
  // 因为Vue需要调用install (Vue.install)
  // 但调用时不需要通过new来实例化
  // Vue在调用install时,会传入参数:Vue构造函数
  static install(_Vue) {
    Vue = _Vue;
    // console.log(Vue);
    // console.log('vue router install');
    Vue.mixin({
      beforeCreate() {
        // 这里会打印两次,因为有俩vue实例(main.js中new Vue,App.vue中的vue),vue会给它们分别添加 beforeCreate 钩子
        // console.log('mixin里的一些数据');

        // 打印后会发现,只有根组件root才有 router 实例,也就是 new VueRouter 这个实例
        // 所以后边我们需要判断,当有router实例时,才执行上边的 init 方法
        console.log('当前vue实例:', this.$options.name, this.$options.router);

        // 找到根Vue实例,然后取出里边的router实例,并执行init方法
        if (this.$options.router) {
          this.$options.router.init();
        }
      }
    });
  }
}

export default VueRouter;