likes
comments
collection
share

Vue-Router原理】从零实现一个简易版Router

作者站长头像
站长
· 阅读数 27

history与hash模式

  • vue-router具有两种路由模式:history、hash模式,这两种模式修改路径的时候,不会触发全部页面的渲染

  • history模式通过window.history对象的pushState方法与replaceState来切换路由。然后通过监听window上的popstate事件,来检测路由历史记录的变化,从而根据最新路径显示对应的页面。

  • hash模式是通过window.localtion对象上的hash来控制路由的变化,(在锚点定位中也需要使用hash)。通过监听window上的hashchange事件,可以得知hash发生变化,再根据hash显示对应的页面即可。

// history
history.pushState(null, null, path);
window.addEventListener('popstate',()=>{})

// hash
<a href="#/home">首页</a>
window.addEventListener('hashchange', ()=>{})

回顾vue-router使用

  • 创建router/index.js

  • 创建路由配置数组

  • 创建VueRouter实例,接收配置数组与路由模式

  • 使用Vue.use进行注册

  • 将vue-router实例作为参数,创建Vue实例

然后就可以在项目中使用router-link与router-view组件,实现路由的跳转与组件的显示功能。

实现路由监听

vue文档中写到,注册插件需要使用提供一个install方法。

  • 当我们使用Vue.use(MyVueRouter)方法时,是将MyVueRouter注册到vue中,vue文档中表示需要插件提供一个install函数,用于注册该插件。

  • 为了让每个组件都能访问router与route实例,需要使用Vue.mixin的beforeCreate声明周期,将这两个对象添加到每个组件中。因为该对象只在vue跟组件实例创建的实例创建,为了让子组件也能访问,可以在子组件中使用this.parent.router与this.parent.router与this.parent.routerthis.parent.route进行访问。

class MyRouter {
  constructor(options) {
    this.mode = ["history", "hash"].includes(options.mode) ? options.mode : 'hash';
    this.routes = options.routes || [];
    // 将路由配置数组转换为map结构
    this.routesMap = {};
    for (let item of this.routes) {
      routeMap[item.path] = item.component;
    }
    // 当前路由信息
    this.routeInfo = null;
    // 监听路由变化
    this.initDefault();
  }
  initDefault() {
    if (this.mode === 'hash') {
      // 1.判断打开的界面有没有hash, 如果没有就跳转到#/
      if (!location.hash) {
        location.hash = '/';
      }
      // 2.pageLoad之后和hash发生变化之后,保存当前路由
      window.addEventListener('load', () => {
        this.routeInfo = location.hash.slice(1);
      });
      window.addEventListener('hashchange', () => {
        this.routeInfo = location.hash.slice(1);
      });
    } else {
      // 3.history模式,页面加载完后判断打开的界面有没有路径, 如果没有就跳转到/
      if (!location.pathname) {
        location.pathname = '/';
      }
      // 4.加载完成之后和history发生变化之后,保存当前路由
      window.addEventListener('load', () => {
        this.routeInfo = location.pathname;
      });
      window.addEventListener('popstate', () => {
        this.routeInfo = location.pathname;
      });
    }
  }
}
// 注册插件
MyRouter.install = (Vue, options) => {
  Vue.mixin({
    beforeCreate() {
      if (this.$options?.router) {
        this.$router = this.$options.router;
        this.$route = this.$router.routeInfo;
      } else {
        this.$router = this.$parent.$router;
        this.$route = this.$router.routeInfo;
      }
    }
  });
}
export default MyRouter;

实现router-link组件

我们在使用vue-router的时候,会使用router-link作为路径跳转的按钮,他其实是一个自定义组件。

在install方法中创建router-link组件,使用vue渲染函数&JSX实现自定义组件

这样就创建了一个带有路径的a标签。

MyRouter.install = (Vue, options) => {
  Vue.component('router-link', {
    props: {
      to: String
    },
    render() {
      //  因为在render方法中的this不是当前组件的实例对象,而是一个proxy代理对象
      //  所以需要通过this._self进行访问router对象
      let path = this.to;
      if (this._self.$router.mode === 'hash') {
        path = '#' + path;
      }
      return <a href={path}>{this.$slots.default}</a>
    }
  });
}

实现router-view组件

当路由发生变化时,router-view组件需要根据路径展示对应的组件。

Vue.component('router-view', {
    render(h) {
        // 拿到路由配置
        let routesMap = this._self.$router.routesMap;
        // 获取当前路径
        let currentPath = this._self.$route;
        // 拿到当前应该展示的组件
        let currentComponent = routesMap[currentPath];
        // 使用vue的h渲染方法,渲染组件
        return h(currentComponent);
    }
});

注意

Vue-Router原理】从零实现一个简易版Router

问题:到达这一步时我们会发现:点击按钮切换路由的时候,router-view没有显示对应的组件。

原因:这是因为当路由发生变化的时候,自定义的router-view组件没有根据最新的数据进行更新。

解决:通过Vue.util.defineReactive(this, 'router', this.router),将this.router),将this.router),将this.router对象实现双向绑定,当点击按钮更换路径的时候,会触发自定义组件router-view的重新渲染,这时候就可以拿到最新的路由信息,展示最新的组件。

  • 在install的beforeCreate声明周期的最后一行加上
Vue.util.defineReactive(this, 'router', this.$router);

到这里,就简单实现了一个vue-router。