手写vue-router之hash模式

248 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第26天,点击查看活动详情

前情提要

前面咱们已经实现了history模式,那么咱们就在原来的基础上实现hash模式。

分析

  • 首先集成两种模式的话那么便要对options中的mode做一个判断了。
  • 其次,因为history模式和hash模式监听的事件是不同的,所以当mode不同的时候,那么我们要注册的全局监听事件也就有所不同。
    • history监听的是popstate事件
    • hash监听的是hashchange事件
    • 在initEvent方法中进行根据mode进行判断,之后再针对性的进行监听
  • 其中我们注意到hash模式会让路由地址中出现一个 # 号。所以我们需要在初始化路由地址的时候默认添加一个 # 号
  • 之后再根据onhashchange监听到的地址,更新data.current实行,然后再与roteMap进行一一映射获取到对应的组件,最后通过render中的h方法将组件进行渲染。
  • 这样一个hash模式就集成了。

具体实现

  • 因为hash模式中浏览器地址栏中多了个#号,所以我们需要在mode为hash时初始化地址栏地址

    initUrl() { // 为地址增加个#号
      let a = location.href.split('#/')
      location.href = `${ a[0] }#/${ a[1] ? a[1] : '' }`
    }
    
  • initComponent方法中的router-link组件的clickHandler方法做如下调整:

    clickHandler(e) {
      e.preventDefault(); // 阻止a标签的默认事件,如果不用a标签实现就不需要这个了。
      // console.log(this) // 这里的this指向的是组件实例
      // console.log(_this) // 这里的this指向的是router类
      const isHash = !_this.options.mode || _this.options.mode === 'hash'// 判断是否是hash模式
      if (isHash) {
        window.location.hash = `#${this.to}` // 注意pushState方法并不会触发hashchange事件,只有直接改变hash值才能触犯
      } else { // 此处依然保留history模式的写法
        history.pushState({}, '', this.to); 
        this.$router.data.current = this.to;
      }
    }
    
  • initComponent方法中的router-view组件的render做如下调整

    Vue.component(
        'router-view',
        {
          render(h) {
            const isHash = !_this.options.mode || _this.options.mode === 'hash'; // 判断是否是hash模式
            const realPath = isHash ? _this.data.current.replace(/^#/, '') : _this.data.current; //如果是hash模式需要去掉地址中的#号,否则无法正确匹配组件。
            const component = _this.routeMap[realPath];
            return h(component)
          }
        }
    )
    
  • initEvent方法中判断mode如果不是history模式则更改监听的事件:

    window.addEventListener('hashchange', () => { // 注意,只有通过直接给location.hash赋值才能触发hashchange事件,通过pushState是无法触发的。
      this.data.current = window.location.hash
    });
    window.addEventListener('load', () => { // 对于hash模式而言重新加载并不会触发hashchange事件,所以需要另外监听load事件
      this.data.current = `window.location.hash
    });
    
  • 到这基本上hash模式就加完了。

  • 不过我又另外加了个push方法的实现,这样在组件里就可以通过:this.$router.push方法实现路由跳转了。

    push(path) {
      const isHash = !this.options.mode || this.options.mode === 'hash'; 同样的需要判断是否是hash模式
      if (isHash) {
        window.location.hash = `#${path}` 如果是直接给location的hash属性赋值,这样就会触发hashchange事件进而改变当前要渲染的组件
      } else {  // history模式没啥好说的。
        history.pushState({}, '', path);
        this.$router.data.current = path;
      }
    }
    

总结