1. 程式人生 > >vue-router 原始碼實現前端路由的兩種方式

vue-router 原始碼實現前端路由的兩種方式

在學習 vue-router 的程式碼之前,先來簡單瞭解一下前端路由。

前端路由主要有兩種實現方法:

  • Hash 路由
  • History 路由
    先來看看這兩種方法的實現原理。

接著我們將用它們來簡單實現一個自己的前端路由。

前端路由

Hash 路由

url 的 hash 是以 # 開頭,原本是用來作為錨點,從而定位到頁面的特定區域。當 hash 改變時,頁面不會因此重新整理,瀏覽器也不會向伺服器傳送請求。

http://www.xxx.com/#/home

同時, hash 改變時,並會觸發相應的 hashchange 事件。所以,hash 很適合被用來做前端路由。當 hash 路由發生了跳轉,便會觸發 hashchange 回撥,回撥裡可以實現頁面更新的操作,從而達到跳轉頁面的效果。

window.addEventListener('hashchange', function () {
 console.log('render');
});

History 路由

HTML5 規範中提供了 history.pushState 和 history.replaceState 來進行路由控制。通過這兩個方法,可以實現改變 url 且不向伺服器傳送請求。同時不會像 hash 有一個 # ,更加的美觀。但是 History 路由需要伺服器的支援,並且需將所有的路由重定向到根頁面。

History 路由的改變不會去觸發某個事件,所以我們需要去考慮如何觸發路由更新後的回撥。

有以下兩種方式會改變 url:

  • 呼叫 history.pushState 或 history.replaceState;
  • 點選瀏覽器的前進與後退。
    第一個方式可以封裝一個方法,在呼叫 pushState(replaceState)後再呼叫回撥。
function push (url) {
 window.history.pushState({}, null, url);
 handleHref();
}
 
function handleHref () {
 console.log('render');
}

第二個方式,瀏覽器的前進與後退會觸發 popstate 事件。

window.addEventListener('popstate', handleHref);

路由實現

我們通過 標籤來進行切換路由,通過一個

標籤來裝載各路由對應的頁面內容。

參考 vue-router 的呼叫,我們會這麼地呼叫一個 Router ,將路由與對應元件作為引數傳入:

const router = new Router([
 {
  path: '/',
  component: 'home'
 },
 {
  path: '/book',
  component: 'book'
 },
 {
  path: '/movie',
  component: 'movie'
 }
]);

數組裡是各路由對應的要顯示的內容,接下來就來開始實現這個 Router 。

Hash 路由實現

Hash 路由 標籤都需要帶上 # :

 <a href="#/" rel="external nofollow" >home</a>
 <a href="#/book" rel="external nofollow" >book</a>
 <a href="#/movie" rel="external nofollow" >movie</a>
   
 <div id="content"></div>
</div>

Router 的程式碼實現如下:

class Router {
 constructor (options) {
  this.routes = {};
   
  this.init();
   
  // 遍歷,繫結檢視更新
  options.forEach(item => {
   this.route(item.path, () => {
    document.getElementById('content').innerHTML = item.component;
   });
  });
 }
  
 // 繫結監聽事件
 init () {
  window.addEventListener('load', this.updateView.bind(this), false);
  window.addEventListener('hashchange', this.updateView.bind(this), false);
 }
  
 // 更新試圖
 updateView () {
  const currentUrl = window.location.hash.slice(1) || '/';
  this.routes[currentUrl] && this.routes[currentUrl]();
 }
  
 // 將路由與回撥函式關聯
 route (path, cb) {
  this.routes[path] = cb;
 }
}

實現效果如下:

History 路由實現

History 路由需要伺服器的支援

<div>
 <a href="javascript:void(0);" rel="external nofollow" rel="external nofollow" rel="external nofollow" data-href="/" rel="external nofollow" >home</a>
 <a href="javascript:void(0);" rel="external nofollow" rel="external nofollow" rel="external nofollow" data-href="/book" rel="external nofollow" >book</a>
 <a href="javascript:void(0);" rel="external nofollow" rel="external nofollow" rel="external nofollow" data-href="/movie" rel="external nofollow" >movie</a>
   
 <div id="content"></div>
</div>

Router 的程式碼實現如下:

class Router {
 constructor (options) {
  this.routes = {};
 
  this.init();
  this.bindEvent();
 
  // 遍歷,繫結檢視更新
  options.forEach(item => {
   this.route(item.path, () => {
    document.getElementById('content').innerHTML = item.component;
   });
  });
 }
 
 // 繫結點選事件
 bindEvent () {
  const _this = this;
  const links = document.getElementsByTagName('a');
 
  [].forEach.call(links, link => {
   link.addEventListener('click', function () {
    const url = this.getAttribute('data-href');
    _this.push(url);
   });
  });
 }
 
 // 繫結監聽事件
 init () {
  window.addEventListener('load', this.updateView.bind(this), false);
  window.addEventListener('popstate', this.updateView.bind(this), false);
 }
 
 push (url) {
  window.history.pushState({}, null, url);
  this.updateView();
 }
 
 // 更新試圖
 updateView () {
  const currentUrl = window.location.pathname || '/';
  this.routes[currentUrl] && this.routes[currentUrl]();
 }
 
 // 將路由與回撥函式關聯
 route (path, cb) {
  this.routes[path] = cb;
 }
}

實現效果如下:

最後

前端路由實現方式有兩種,分別是:

  • Hash 路由
  • History 路由
    原理都是修改 url 的同時不重新整理頁面,不向伺服器傳送請求,通過監聽特殊的事件來更新頁面。