什么是路由
簡(jiǎn)單來(lái)說(shuō)路由就是用來(lái)跟后端服務(wù)器進(jìn)行交互的一種方式,通過(guò)不同的路徑,來(lái)請(qǐng)求不同的資源(if...else...),給我一個(gè)路徑,我給你返回一個(gè)響應(yīng),請(qǐng)求不同的頁(yè)面是路由的其中一種功能。
前端路由
我們通過(guò)一個(gè)狀態(tài)切換的例子來(lái)理解前端路由,代碼如下:
// 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')
})
這樣實(shí)現(xiàn)了點(diǎn)擊tab-1,出現(xiàn)對(duì)應(yīng)的 content-1 的內(nèi)容,點(diǎn)擊tab-2,出現(xiàn)對(duì)應(yīng)的 content-2 的內(nèi)容的功能。
現(xiàn)在,我們先設(shè)置 tab-1的狀態(tài)為 active,content-1 也為 active。刷新頁(yè)面,我們點(diǎn)擊 tab-2,tab-2 就被激活了。這個(gè)時(shí)候,我們刷新頁(yè)面或者是將此頁(yè)面分享給別人,打開(kāi)頁(yè)面以后又回到了 tab-1,這樣就會(huì)出現(xiàn)同樣的 url 看到的界面卻是不一樣的。那么如何才能使你的界面狀態(tài)是可分享?
1. 使用哈希來(lái)保存當(dāng)前頁(yè)面狀態(tài)信息
通過(guò) index 來(lái)記錄用戶點(diǎn)擊的是第幾個(gè) tab, 使用 hash 來(lái)記錄這個(gè)狀態(tài)。
// 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(); //點(diǎn)的是第幾個(gè)tab
location.hash = index // 1
$li.closest('x-tab').find('.content > li').eq(index).addClass('active')
.siblings().removeClass('active')
})
通過(guò)三個(gè)步驟:
- 設(shè)置 hash
- 讀取 hash
- 分享 hash
這樣呢我們的界面狀態(tài)就可以通過(guò)錨點(diǎn)來(lái)記錄了,將鏈接復(fù)制到另一個(gè)窗口上打開(kāi)依然是原來(lái)的狀態(tài),此時(shí)就簡(jiǎn)單的實(shí)現(xiàn)了,刷新頁(yè)面當(dāng)前狀態(tài)不改變,同時(shí)當(dāng)前狀態(tài)可分享給別人。
2. 使用 a 標(biāo)簽 和監(jiān)聽(tīng)哈希變更事件
上面的例子中,其實(shí)我們點(diǎn)擊事件保存的就是形如后綴為 #0 和 #1 這樣的 url。有沒(méi)有另外一種可能,既然我們是通過(guò)錨點(diǎn)來(lái)切換 tab 的話,那能不能用 a 標(biāo)簽來(lái)做呢?
這樣我們點(diǎn)擊 tab 的時(shí)候更改變化的是 url,就不去監(jiān)聽(tīng) click 事件了,我們監(jiān)聽(tīng)什么事件呢?是哈希變更事件(hashchange),就是如果 a 標(biāo)簽點(diǎn)擊之后哈希是1,就把第一個(gè)添加上一個(gè)紅色背景。代碼如下:
// 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');
}
這種方法代碼也精簡(jiǎn)了很多,其實(shí)就 3 行代碼,首先選擇下 tab,當(dāng)哈希變化的時(shí)候,再選擇一下 tab 。
但是這個(gè)哈希還有很大的問(wèn)題,就是如果還有一個(gè)回到頂部的鏈接。原本我們已經(jīng)選中了 tab-2 ,但是點(diǎn)擊了回到頂部以后原來(lái)的狀態(tài)被覆蓋了,因此在刷新頁(yè)面或其他窗口打開(kāi)的時(shí)候就沒(méi)有了原來(lái)的狀態(tài)。
3. 使用路徑來(lái)代替哈希
在上面的例子中,如果在 a 標(biāo)簽中使用錨點(diǎn)表示某個(gè)狀態(tài),容易被其他的錨點(diǎn)所覆蓋。那么我們直接使用路徑來(lái)代替哈希,當(dāng)點(diǎn)擊 tab1 就跳轉(zhuǎn)到 <a href="./tab1">,當(dāng)點(diǎn)擊 tab2 就跳轉(zhuǎn)到 <a href="./tab2">。
但是這個(gè)請(qǐng)求不可能成功,因?yàn)楹笈_(tái)根本就沒(méi)有響應(yīng) './tab1'和'./tab2'這個(gè)路徑。當(dāng)點(diǎn)擊 tab1 的時(shí)候它會(huì)跳頁(yè)面,就會(huì)去請(qǐng)求 tab1 這個(gè)頁(yè)面,返回的是 404。
實(shí)際上我們的目的不是跳轉(zhuǎn)頁(yè)面只是想改變 url,沒(méi)關(guān)系,我么可以阻止 a 標(biāo)簽的默認(rèn)事件,不要它跳轉(zhuǎn)(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');
}
}
這樣我們點(diǎn)擊 tab1 或者 tab2 的時(shí)候,就只是改了 url,沒(méi)有跳轉(zhuǎn)頁(yè)面這就叫做巧用 History API無(wú)刷新更改地址欄。
現(xiàn)在的問(wèn)題是此時(shí)的頁(yè)面狀態(tài)是不可分享的,返回 404,后臺(tái)沒(méi)有這個(gè)路徑的路由信息。因?yàn)樗械?url 都是先給服務(wù)器過(guò)一遍,然后再給 js 的。
這個(gè)時(shí)候我們可以自己寫(xiě)一個(gè)后端路由來(lái)模擬實(shí)現(xiàn)刷新頁(yè)面后不跳轉(zhuǎn)頁(yè)面,當(dāng)路徑為 / 或者/tab1 或者 /tab2 的時(shí)候都是返回同一個(gè)頁(yè)面。
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()
}
這樣就不會(huì)去通知服務(wù)器,js 做的假的頁(yè)面跳轉(zhuǎn),同時(shí)把頁(yè)面更新到對(duì)應(yīng)的狀態(tài)。
現(xiàn)在我們應(yīng)該可以知道了
1. 路由就是給我一個(gè)路徑,我給你返回一個(gè)響應(yīng)
2. 前端路由就是前端頁(yè)面做這個(gè)事情,前端做路由
3. 后端路由呢就是后端做路由
以上的前端路由,也就是 Vue-Router 中 <router-link></router-link>的實(shí)現(xiàn)原理。