手写vue-router源码 实现属于自己的路由

1,743 阅读1分钟

先简单说一说,我们前端路由的实现主要是为了SPA应用的框架,框架开发都是单页应用,单页应用的特点就是跳转页面的时候的不刷新浏览器,那想要实现跳转页面不刷新浏览器的方式有两种:一种是通过hash的方式、另一种就是通过H5的history的api实现

路由实现的核心原理

  • hash的实现原理
<a href="#/home">首页</a>
<a href="#/about">关于</a>
<div id="html"></div>

<script>
    window.addEventListener('load',()=>{
        html.innerHTML = location.hash.slice(1);
    });
    window.addEventListener('hashchange',()=>{
        html.innerHTML = location.hash.slice(1)
    })
</script>
  • history api的实现原理
<a onclick="go(/home)">首页</a>
<a onclick="go(/about)">关于</a>
<div id="html"></div>

<script >
    function go(pathname) {
        history.pushState({},null,pathname)
        html.innerHTML = pathname;
    }
    window.addEventListener('popstate',()=>{
        go(location.pathname);
    })
</script>

源码实现

  • $options下的router对象是我们在实例化Vue的时候挂载的那个vue-router实例;
  • _route是一个响应式的路由route对象,这个对象会存储我们路由信息,它是通过Vue提供的Vue.util.defineReactive来实现响应式的,下面的get和set便是对它进行的数据劫持;
  • _router存储的就是我们从$options中拿到的vue-router对象;
  • _root指向我们的Vue根节点;
  • route和$router是定义在Vue.prototype上的两个getter。前者指向_root下的_route,后者指向_root下的_router
class HistoryRoute{
    constructor(){
        this.current = null;
    }
}
class  vueRouter {
  constructor(options){
      this.mode = options.mode || "hash";
      this.routes = options.routes || [];
      // 传递的路由表是数组 需要装换成{'/home':Home,'/about',About}格式
      this.routesMap = this.createMap(this.routes);
      // 路由中需要存放当前的路径  需要状态
      this.history = new HistoryRoute;
      this.init();//开始初始化操作
  }
  init(){
      if(this.mode == 'hash'){
          // 先判断用户打开时有没有hash,没有就跳转到#/
          location.hash?'':location.hash = '/';
          window.addEventListener('load',()=>{
              this.history.current = location.hash.slice(1);
          });
          window.addEventListener('hashchange',()=>{
              this.history.current = location.hash.slice(1);
          })
      }else {
          location.pathname?'':location.pathname = '/';
          window.addEventListener('load',()=>{
              this.history.current = location.pathname;
          });
          window.addEventListener('popstate',()=>{
              this.history.current = location.pathname;
          })
          
      }
  }
  createMap(routes){
      return routes.reduce((memo,current)=>{
          memo[current.path] = current.component
          return memo
      },{})
  }
}
//使用vue.use就会调用install方法
vueRouter.install = function(Vue,opts) {
    //每个组件都有 this.$router / this.$route 所以要mixin一下
    Vue.mixin({
        beforeCreate(){ //混合方法
            if(this.$options && this.$options.router){//定位跟组件
                this._root = this;//把当前实例挂载在_root上
                this._router = this.$options.router // 把router实例挂载在_router上
                //history中的current变化也会触发
                Vue.util.defineReactive(this,'xxx',this._router.history);
            }else {
                // vue组件的渲染顺序  父 -> 子 -> 孙子
                this._root =  this.$parent._root;//获取唯一的路由实例
            }
            Object.defineProperty(this,'$router',{//Router的实例
                get(){
                    return this._root._router;
                }
            });
            Object.defineProperty(this,'$route',{
                get(){
                    return {
                        //当前路由所在的状态
                        current:this._root._router.history.current
                    }
                }
            })
        }
    });
    // 全局注册 router的两个组件
    Vue.component('router-link',{
        props:{
            to:String,
            tag:String
        },
        methods:{
            handleClick(){跳转方法
                
            }
        },
        render(h){
            let mode = this._self._root._router.mode;
            let tag = this.tag;
            return <tag on-click={this.handleClick} href={mode === 'hash'?`#${this.to}`:this.to}>{this.$slots.default}</tag>
        }
    })
    Vue.component('router-view',{//根据当前的状态 current 对应相应的路由
        render(h){
            //将current变成动态的 current变化应该会影响视图刷新
            //vue实现双向绑定 重写Object.defineProperty
            let current = this._self._root._router.history.current;
            let routeMap = this._self._root._router.routesMap
            return h(routeMap[current])
        }
    })
}
export  default VueRouter;