前言
在單頁面應用程序中,前后端采用了完全分離的方法,因此在前端實現路由的切換非常的重要。同時前端實現路由可以減少請求數,緩解后端的壓力。在單頁面中的路由主要有兩種實現方法,一種是通過h5的history api來實現,還有一種是hash來實現。
history
history的方法主要是應用了幾個H5的histroy API:
API
- window.pushState(stateData, title, url)
在history中創建一個新的訪問記錄,不能跨域,且不造成頁面刷新,stateData為當前頁面的一些信息在觸發popstate事件的時候可以調用;title為網頁的標題;url是瀏覽器中顯示的網址。
- window.replaceState(stateData, title, url)
修改當前的訪問記錄,不能跨域,且不造成頁面刷新
- windows事件popstate
當回到頁面前一頁或者后一頁的時候觸發
API示意圖
這張圖表示了這些api的具體操作。
pushState主要就是向當前頁面的后面添加一個新的頁面,新的頁面地址是第三個參數。
replaceState是把當前頁面的地址替換成他的第三個參數,history並不會記錄之前的地址
這兩個方法都不能進行頁面的刷新,所以需要在調用方法的時候執行一下相關路由變化的操作,從而達到頁面不切換的效果更改頁面的單頁面效果。
舉個栗子
因此只要在頁面跳轉的時候改成pushState然后執行當前頁面的加載函數,就可以實現不刷新的頁面跳轉了如:
原來地址是:https://www.baidu.com
然后點擊了按鈕進行頁面跳轉就執行函數:
history.pushState(data, title, 'https://www.baidu.com/test')
然后瀏覽器上面的地址已經變成了https://www.baidu.com/test然后同時還有返回的按鈕
然后調用一下test頁面的渲染方法
存在問題
這樣子會存在一個問題,就是當頁面直接打開https://www.baidu.com/test服務器會找不到這個頁面而報404的錯
解決問題
對此錯誤我想到了兩種解決方法
-
其一是你在服務器端設置一下當訪問https://www.baidu.com/以下的所有地址全部轉發到https://www.baidu.com/這個地址上來。然后前端通過對url的解析來確定用戶想訪問的頁面,然后渲染這個頁面
-
其二是你跳轉頁面用不同參數的形式來跳轉頁面,如
原來地址是:https://www.baidu.com
然后點擊跳轉的時候執行
history.pushState(data, title, '?page=test')
然后瀏覽器地址欄的地址是https://www.baidu.com?page=test
然后當你訪問https://www.baidu.com?page=test頁面的時候會打開https://www.baidu.com然后你可以根據page參數的值來判斷用戶想訪問的頁面,進行不同的渲染
使用了解決方法二寫的一個history單頁面的小栗子
html代碼
<div id="msg"></div>
<br><br>
<span class="msgBtn" data-route='A'>toFirst</span>
<span class="msgBtn" data-route='B'>toSecond</span>
<span class="msgBtn" data-route='C'>toThird</span>
樣式很簡單就是一個顯示信息的msg然后三個按鈕對應不同的頁面
js代碼
//路由注冊
const message = {
undefined: 'massage',
A: 'hahahh',
B: 'hehehehe',
C: 'hohohoho'
};
//頁面分發加載
function showMsg(el) {
const _loc = location.href;
let url_msg_id = GetRequest(_loc);
el.html(message[url_msg_id.msg]);
}
//監聽前進后退按鈕
window.addEventListener('popstate', function (e) {
showMsg($('#msg'));
});
//點擊切換路由
$('.msgBtn').click(function (e) {
console.log(e.currentTarget );
history.pushState(null, null, '?msg=' + e.target.dataset.route);
showMsg($('#msg'));
});
//處理url
function GetRequest() {
let url = location.search; //獲取url中"?"符后的字串
let theRequest = {};
if (url.indexOf("?") !== -1) {
let str = url.substr(1);
let strs = str.split("&");
for (let i = 0; i < strs.length; i++) {
theRequest[strs[i].split("=")[0]] = unescape(strs[i].split("=")[1]);
}
}
return theRequest;
}
//頁面加載渲染
showMsg($('#msg'));
- 在注冊路由里面的這個對象是每個路由名對應不同的信息,然后如果是頁面的話可以每個路由名對應不同頁面的渲染方法,其中undefined是沒有參數的時候的頁面。
- showMsg方法在頁面加載和路由切換的時候執行,作用是根據當前的地址來渲染不同的頁面,這里只是用了$().html()來更改頁面,如果是不同的頁面的話可以來執行不同的渲染方法
- 監聽popstate的作用是點擊瀏覽器的前進后退的時候執行一下渲染方法
- 點擊事件是點擊不同的按鈕來更改地址然后觸發渲染方法
- GetRequest方法是一個提取url中的參數的方法
實現過程
當你點擊toFirst會把url的msg參數變成toFirst對應的data-route,然后觸發showMsg方法,獲取到當前的url的參數在message中找到對應要渲染的字符,渲染在msg上。
效果如下
然后點擊toSecond
點擊瀏覽器的返回按鈕,通過監聽來重新調用showMsg方法,所以會回到這個頁面
如果你直接打開http://localhost:63342/history/index.html?msg=C
也會在index.html里面進行showMsg處理來打開對應的頁面
完整代碼
<html>
<head>
<title></title>
<style type="text/css">
div {
margin: 10px;
}
.msgBtn {
margin: 10px;
padding: 10px;
border: 1px solid black;
}
</style>
</head>
<body>
<div id="msg"></div>
<br><br>
<span class="msgBtn" data-route='A'>toFirst</span>
<span class="msgBtn" data-route='B'>toSecond</span>
<span class="msgBtn" data-route='C'>toThird</span>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script type="text/javascript">
//路由注冊
const message = {
undefined: 'massage',
A: 'hahahh',
B: 'hehehehe',
C: 'hohohoho'
};
//頁面分發加載
function showMsg(el) {
const _loc = location.href;
let url_msg_id = GetRequest(_loc);
el.html(message[url_msg_id.msg]);
}
//監聽前進后退按鈕
window.addEventListener('popstate', function (e) {
showMsg($('#msg'));
});
//點擊切換路由
$('.msgBtn').click(function (e) {
console.log(e.currentTarget );
history.pushState(null, null, '?msg=' + e.target.dataset.route);
showMsg($('#msg'));
});
//處理url
function GetRequest() {
let url = location.search; //獲取url中"?"符后的字串
let theRequest = {};
if (url.indexOf("?") !== -1) {
let str = url.substr(1);
let strs = str.split("&");
for (let i = 0; i < strs.length; i++) {
theRequest[strs[i].split("=")[0]] = unescape(strs[i].split("=")[1]);
}
}
return theRequest;
}
//頁面加載渲染
showMsg($('#msg'));
</script>
</body>
hash
實現思路和之前的大概相同就是變化后面的哈希值,然后通過hashchange監聽到哈希值的變化
demo地址 https://github.com/WindStormrage/Single-page-application/blob/master/hash/index.html
總結
這樣子是大概實現出了一個單頁面的效果但是,還有幾個單頁面的要素沒有體現出來,比如異步靜態文件的加載。
然后hash的缺點是,只好設置一層路由如果還有子頁面需要路由會比較麻煩,然后還有的是就是#這個字符讓url顯得非常不美觀