什么是路由
簡單來說路由就是用來跟后端服務器進行交互的一種方式,通過不同的路徑,來請求不同的資源(if...else...),給我一個路徑,我給你返回一個響應,請求不同的頁面是路由的其中一種功能。
前端路由
我們通過一個狀態切換的例子來理解前端路由,代碼如下:
// HTML
<x-tab>
<ol class="nav">
<li>tab 1</li>
<li>tab 2</li>
</ol>
<ol class="content">
<li>content 1</li>
<li>content 2</li>
</ol>
</x-tab>
// CSS
x-tab{ display: block; }
x-tab > .nav > li.active{ background: red; }
x-tab > .content > li{ display: none; }
x-tab > .content > li.active{ display: block; }
// js(引入jquery)
$('x-tab').on('click', '.nav > li', (e)=>{
let $li = $(e.currentTarget);
$li.addClass('active').siblings().removeClass('active');
let index = $li.index();
$li.closest('x-tab').find('.content > li').eq(index).addClass('active')
.siblings().removeClass('active')
})
這樣實現了點擊tab-1,出現對應的 content-1 的內容,點擊tab-2,出現對應的 content-2 的內容的功能。
現在,我們先設置 tab-1的狀態為 active,content-1 也為 active。刷新頁面,我們點擊 tab-2,tab-2 就被激活了。這個時候,我們刷新頁面或者是將此頁面分享給別人,打開頁面以后又回到了 tab-1,這樣就會出現同樣的 url 看到的界面卻是不一樣的。那么如何才能使你的界面狀態是可分享?
1. 使用哈希來保存當前頁面狀態信息
通過 index 來記錄用戶點擊的是第幾個 tab, 使用 hash 來記錄這個狀態。
// HTML和CSS不變
// js
let index = location.hash || '#0' //2
index = index.substring(1) //3
$('x-tab > .nav > li').eq(index).addClass('active').siblings()
.removeClass('active');
$('x-tab > .content > li').eq(index).addClass('active').siblings()
.removeClass('active');
$('x-tab').on('click', '.nav > li', (e)=>{
let $li = $(e.currentTarget);
$li.addClass('active').siblings().removeClass('active');
let index = $li.index(); //點的是第幾個tab
location.hash = index // 1
$li.closest('x-tab').find('.content > li').eq(index).addClass('active')
.siblings().removeClass('active')
})
通過三個步驟:
- 設置 hash
- 讀取 hash
- 分享 hash
這樣呢我們的界面狀態就可以通過錨點來記錄了,將鏈接復制到另一個窗口上打開依然是原來的狀態,此時就簡單的實現了,刷新頁面當前狀態不改變,同時當前狀態可分享給別人。
2. 使用 a 標簽 和監聽哈希變更事件
上面的例子中,其實我們點擊事件保存的就是形如后綴為 #0 和 #1 這樣的 url。有沒有另外一種可能,既然我們是通過錨點來切換 tab 的話,那能不能用 a 標簽來做呢?
這樣我們點擊 tab 的時候更改變化的是 url,就不去監聽 click 事件了,我們監聽什么事件呢?是哈希變更事件(hashchange),就是如果 a 標簽點擊之后哈希是1,就把第一個添加上一個紅色背景。代碼如下:
// HTML
<x-tab>
<ol class="nav">
<li><a href="#0"> tab 1 </a></li>
<li><a href="#1"> tab 2 </a></li>
</ol>
<ol class="content">
<li>content 1</li>
<li>content 2</li>
</ol>
</x-tab>
// js
selectTab()
window.onhashchange = (e)=>{
selectTab()
}
function selectTab(){
let index = location.hash || '#0'
index = index.substring(1)
$('x-tab > .nav > li').eq(index).addClass('active').siblings()
.removeClass('active');
$('x-tab > .content > li').eq(index).addClass('active').siblings()
.removeClass('active');
}
這種方法代碼也精簡了很多,其實就 3 行代碼,首先選擇下 tab,當哈希變化的時候,再選擇一下 tab 。
但是這個哈希還有很大的問題,就是如果還有一個回到頂部的鏈接。原本我們已經選中了 tab-2 ,但是點擊了回到頂部以后原來的狀態被覆蓋了,因此在刷新頁面或其他窗口打開的時候就沒有了原來的狀態。
3. 使用路徑來代替哈希
在上面的例子中,如果在 a 標簽中使用錨點表示某個狀態,容易被其他的錨點所覆蓋。那么我們直接使用路徑來代替哈希,當點擊 tab1 就跳轉到 <a href="./tab1">,當點擊 tab2 就跳轉到 <a href="./tab2">。
但是這個請求不可能成功,因為后臺根本就沒有響應 './tab1'和'./tab2'這個路徑。當點擊 tab1 的時候它會跳頁面,就會去請求 tab1 這個頁面,返回的是 404。
實際上我們的目的不是跳轉頁面只是想改變 url,沒關系,我么可以阻止 a 標簽的默認事件,不要它跳轉(e.preventDefault())。
// HTML
<x-tab>
<ol class="nav">
<li><a href="./tab1"> tab 1 </a></li>
<li><a href="./tab2"> tab 2 </a></li>
</ol>
<ol class="content">
<li>content 1</li>
<li>content 2</li>
</ol>
</x-tab>
// js
selectTab()
$('x-tab').on('click', '.nav > li > a', (e)=>{
e.preventDefault();
let a = e.currentTarget;
let path = a.getAttribute('href');
window.history.pushState({], xxx, path);
selectTab();
})
function selectTab(){
let index = location.pathname.substring(1) || 'tab1';
index = index.substring(3);
if(index === 1){
$('x-tab > .nav > li').eq(0).addClass('active').siblings()
.removeClass('active');
$('x-tab > .content > li').eq(0).addClass('active').siblings()
.removeClass('active');
}else if(index === 2){
$('x-tab > .nav > li').eq(1).addClass('active').siblings()
.removeClass('active');
$('x-tab > .content > li').eq(1).addClass('active').siblings()
.removeClass('active');
}
}
這樣我們點擊 tab1 或者 tab2 的時候,就只是改了 url,沒有跳轉頁面這就叫做巧用 History API無刷新更改地址欄。
現在的問題是此時的頁面狀態是不可分享的,返回 404,后臺沒有這個路徑的路由信息。因為所有的 url 都是先給服務器過一遍,然后再給 js 的。
這個時候我們可以自己寫一個后端路由來模擬實現刷新頁面后不跳轉頁面,當路徑為 / 或者/tab1 或者 /tab2 的時候都是返回同一個頁面。
if(path === '/' || path === '/tab1' || path === '/tab2'){
response.statusCode = 200
response.setHeader('Content-Type', 'text/html;charset=utf-8')
let string = fs.readFileSync('./index.html','utf8')
response.write(string)
response.end()
}else{
response.statusCode = 404
response.setHeader('Content-Type', 'text/html;charset=utf-8')
response.write('嗚嗚嗚')
response.end()
}
這樣就不會去通知服務器,js 做的假的頁面跳轉,同時把頁面更新到對應的狀態。
現在我們應該可以知道了
1. 路由就是給我一個路徑,我給你返回一個響應
2. 前端路由就是前端頁面做這個事情,前端做路由
3. 后端路由呢就是后端做路由
以上的前端路由,也就是 Vue-Router 中 <router-link></router-link>的實現原理。