重寫vue源碼:vue-router的實現

0.前端路由原理

1.history模式原理:
利用history.pushState這個api,將路徑推動棧中,頁面不會刷新,網址會發生改變。而後window監聽load和popstate事件,根據location.pathname的值,來替換router-view的內容。javascript

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title></title>
  </head>
  <body>
    <a href="/home">主頁</a>
    <a href="/about">關於</a>
    <div class="router-view"></div>
    <script src="./history.js"></script>
  </body>
</html>
const router_view = document.querySelector('.router-view');
const a_list = document.querySelectorAll('a');
for (let i = 0; i < a_list.length; i++) { 
 
   
  a_list[i].onclick = (e) => { 
 
   
    e.preventDefault();
    const href = e.target.getAttribute('href') || '/';
    history.pushState({ 
 
   }, null, path);
    routerChange(href);
  };
}
window.addEventListener('load', () => { 
 
   
  routerChange(location.pathname);
});
window.addEventListener('popstate', () => { 
 
   
  routerChange(location.pathname);
});
function routerChange(path) { 
 
   
  switch (path) { 
 
   
    case '/home':
      router_view.innerHTML = 'Home';
      break;
    case '/about':
      router_view.innerHTML = 'About';
      break;
    default:
      router_view.innerHTML = '';
      break;
  }
}

2.hash路由原理:
瀏覽器改變網址後面的#/xxx部分是不會刷新頁面的,咱們就能夠經過監聽hashchange事件,從而變動router-view裏面的內容。html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title></title>
  </head>
  <body>
    <a href="#/home">主頁</a>
    <a href="#/about">關於</a>
    <div class="router-view"></div>
    <script src="./hash.js"></script>
  </body>
</html>
const router_view = document.querySelector('.router-view');
window.addEventListener('load', () => { 
 
   
  routerChange(location.hash.slice(1));
});
window.addEventListener('hashchange', () => { 
 
   
  routerChange(location.hash.slice(1));
});
function routerChange(path) { 
 
   
  switch (path) { 
 
   
    case '/home':
      router_view.innerHTML = 'Home';
      break;
    case '/about':
      router_view.innerHTML = 'About';
      break;
    default:
      router_view.innerHTML = '';
      break;
  }
}

1.解析vue-router

vue-router的基本使用:前端

import Vue from 'vue';
import VueRouter from '../extends/my-router';
import Home from '../views/Home.vue';

Vue.use(VueRouter);

const routes = [
  { 
 
   
    path: '/',
    name: 'Home',
    component: Home,
  },
  { 
 
   
    path: '/about',
    name: 'About',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () =>
      import(/* webpackChunkName: "about" */ '../views/About.vue'),
  },
];

const router = new VueRouter({ 
 
   
  mode: 'hash',
  routes,
});

export default router;
  1. Vue.use(VueRouter):表示VueRouter是一個插件,它是一個方法或者VueRouter.install是一個方法
  2. new VueRouter(): 表示VueRouter是一個構造函數,因此它具備VueRouter.install這個一個方法。

2.重寫vue-router

class HistoryRoute { 
 
   
  constructor() { 
 
   
    this.current = null;
  }
}
class VueRouter { 
 
   
  constructor(options) { 
 
   
    this.mode = options.mode || 'hash';
    this.routes = options.routes || [];
    this.routeMap = this.createMap(this.routes);
    this.history = new HistoryRoute();
    this.init();
  }
  init() { 
 
   
    if (this.mode === 'history') { 
 
   
      window.addEventListener('popstate', () => { 
 
   
        this.history.current = location.pathname ? location.pathname : '/';
      });
      window.addEventListener('load', () => { 
 
   
        this.history.current = location.pathname ? location.pathname : '/';
      });
    } else { 
 
   
      window.addEventListener('hashchange', () => { 
 
   
        this.history.current = location.hash ? location.hash.slice(1) : '/';
      });
      window.addEventListener('load', () => { 
 
   
        this.history.current = location.hash ? location.hash.slice(1) : '/';
      });
    }
  }
  createMap(routes) { 
 
   
    const routeMap = { 
 
   };
    routes.forEach((route) => { 
 
   
      routeMap[route.path] = route.component;
    });
    return routeMap;
  }
}
VueRouter.install = function(Vue) { 
 
   
  Vue.mixin({ 
 
   
    beforeCreate() { 
 
   
      if (this.$options && this.$options.router) { 
 
    // 跟組件組件
        this._root = this;
        this._router = this.$options.router;
        // 咱們須要將this._router.history變成響應式的,這樣才能動態改變組件
        Vue.util.defineReactive(this, 'xxx', this._router.history);
      } else { 
 
    // 子組件
        this._root = this.$parent && this.$parent._root;
      }
      // 在每個組件實例上添加$router
      Object.defineProperty(this, '$router', { 
 
   
        get: () => { 
 
   
          return this._root._router;
        },
      });
      // 在每一個組件實例上添加$route
      Object.defineProperty(this, '$route', { 
 
   
        get: () => { 
 
   
          return this._root_router.history.current;
        },
      });
      // 註冊router-link組件
      Vue.component('router-link', { 
 
   
        props: { 
 
   
          to: String,
        },
        render(h) { 
 
   
          return h('a', { 
 
    attrs: { 
 
    href: this.to } }, this.$slots.default);
        },
      });
      // 註冊router-view組件
      Vue.component('router-view', { 
 
   
        render(h) { 
 
   
          console.log(this.$router);
          console.log(this.$router.history.current);
          return h(this.$router.routeMap[this.$router.history.current]);
        },
      });
    },
  });
};
export default VueRouter;

本文同步分享在 博客「冬天愛吃冰淇淋」(CSDN)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。vue

相關文章
相關標籤/搜索