手写vue-router之history模式【五、终版】

137 阅读3分钟

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

前情提要

复盘前面遇到的问题

  • 首先遇到了全局注册的组件无法被编译的问题,主要是因为为了性能问题一般情况下vue-cli并不提供编译器,只提供运行时版本。因为编译器有10k的大小,对性能有一定的损耗。而作为解决方案,我们可以选择开启添加编译器的配置选项,但是因此带来的性能问题要自费哦。其次我们可以通过render函数代替template模板,结合render中的h函数来实现组件的编译。这种方式也是推荐的方式,不会造成性能损耗。
  • 解决了上面的问题之后,我们在点击按钮的时候会发现页面有闪动。究其原因这是因为每次点击浏览器向端发起了请求。这并不是我们想要的,所以我们需要解决这个问题。
    • 也就是在initComponents方法中在render函数中为标签注册一个点击事件,在点击事件中阻止默认事件。
  • 除了上面那个问题还有一个问题,那就是还需要实现一个router-view组件,通过router-view组件来实现组件占位符。也就是未来组件要渲染的区域

实现

  • 关于编译的问题我们选择通过render来解决。

  • 我们在render中为a标签添加点击事件,取消默认事件。

    Vue.component(
        'router-link', 
        {
          props: {
              to: String
          },
          render(h) {
            return h('a', { attrs: { href: this.to }, on: { click: this.clickHandler } }, [ this.$slots.default ])
          },
          methods: {
            clickHandler(e) {
              // 将当前页面的地址记录到浏览器的历史中去
              // pushState接收三个参数,第一个是状态值,第二个是网站标题,第三个是路径
              history.pushState({}, '', this.to);
              // 更新当前路由的信息
              this.$router.data.current = this.to// 阻止默认事件
              e.preventDefault()
            }
          }
        }
    )
    
  • 其次我们还需要实现router-view组件

    Vue.component('router-view', {
        // 根据当前路由地址从路由地址和组件地址映射表中获取组件地址
        const component = this.routeMap[this.data.current]; 
        // 主要借助h函数会帮我们自动把组件进行编译成虚拟dom
        render: h => h(component);
    })
    
  • 将上述的router-view组件也放入到initComponents方法中

  • 到这两个自带的组件我们就完成了。但是我们运行之后会发现还是有问题...

    • 当我们点击浏览器的前进和后退按钮时,地址栏中的地址发生了变化但是应该渲染的相对应的组件并没有渲染。
    • 这个问题的原因是浏览器的前进和后退的操作并没有触发data属性的变化。
    • 在第一篇文章中写到过popstate事件会监听到浏览器的前进和后退事件。因此我们还需要借助popState方法来实现监听。
    • 解决方法:
      initEvent() {
          // 监听popstate事件
          window.addEventListener('popstate', () => {
              // 获取当前浏览器的地址名称,然后赋值给router类中的data的current属性
              this.data.current = window.location.pathname
          })
      }
      
    • 之后再将该方法放到init方法中。随同该插件注册的时候一起被执行。
  • 至此一个简易版的vue-router的轮子我们便造好了。