瞭解SPA
現代前端項目多爲單頁Web應用(SPA),在單頁Web應用中路由是其中的重要環節。
SPA 是 single page web application 的簡稱,譯爲單頁Web應用。
簡單的說 SPA 就是一個WEB項目只有一個 HTML 頁面,一旦頁面加載完成,SPA 不會因爲用戶的操作而進行頁面的重新加載或跳轉。 取而代之的是利用 JS 動態的變換 HTML 的內容,從而來模擬多個視圖間跳轉。
前度路由
簡單的說,就是在保證只有一個 HTML 頁面,且與用戶交互時不刷新和跳轉頁面的同時,爲 SPA 中的每個視圖展示形式匹配一個特殊的 url。在刷新、前進、後退和SEO時均通過這個特殊的 url 來實現。
我們需要實現下滿兩點:
- 改變 url 且不讓瀏覽器像服務器發送請求。
- 可以監聽到 url 的變化
- 可以在不刷新頁面的前提下動態改變瀏覽器地址欄中的URL地址
hash 模式和 history 模式,就是用來實現上面功能的
Hash模式
在url後面加上#,如http://127.0.0.1:5500/前端路由/hash.html#/page1
這個url後面的#/page1
就是hash值
- hash 值的變化不會導致瀏覽器像服務器發送請求
- location.hash可以獲取hash值
- hashchange是hash值發生改變的調用的函數
基於以上三點我們可以寫一個路由實例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<ul>
<li><a href="#/">/</a></li>
<li><a href="#/page1">page1</a></li>
<li><a href="#/page2">page2</a></li>
</ul>
<div class="content-div"></div>
</body>
<script>
class RouterClass {
constructor() {
this.routes = {}; // 記錄路徑標識符對應的cb
this.currentUrl = ""; // 記錄hash只爲方便執行cb
window.addEventListener("load", () => this.render());
window.addEventListener("hashchange", () => this.render());
}
/* 初始化 */
static init() {
window.Router = new RouterClass();
}
/* 註冊路由和回調 */
route(path, cb) {
this.routes[path] = cb || function() {};
}
/* 記錄當前hash,執行cb */
render() {
this.currentUrl = window.location.hash.slice(1) || "/";
this.routes[this.currentUrl]();
}
}
RouterClass.init();
const ContentDom = document.querySelector(".content-div");
const changeContent = content => (ContentDom.innerHTML = content);
Router.route("/", () => changeContent("默認頁面"));
Router.route("/page1", () => changeContent("page1頁面"));
Router.route("/page2", () => changeContent("page2頁面"));
</script>
</html>
History模式
History 接口允許操作瀏覽器的曾經在標籤頁或者框架裏訪問的會話歷史記錄。可以參考下兩篇文章對history的說明
https://css-tricks.com/using-the-html5-history-api/
https://developer.mozilla.org/zh-CN/docs/Web/API/History
下面介紹在這個模式下需要用到的api
history基本api
-
history.go(n)
:路由跳轉幾步,n爲2往前跳轉2個頁面,-2往後跳轉兩個頁面 -
history.back()
:路由後退,相當於history.go(-1)
,用戶可點擊瀏覽器左上角的後退按鈕模擬此方法 -
history.forward()
:路由前進,相當於history.go(1)
,用戶可點擊瀏覽器左上角的前進按鈕模擬此方法
pushState()
history.pushState()
:添加一條路由歷史記錄,如果設置跨域網址則報錯
history.pushState
用於在瀏覽歷史中添加歷史記錄,但是並不觸發跳轉,此方法接受三個參數,依次爲:state
:一個與指定網址相關的狀態對象,popstate
事件觸發時,該對象會傳入回調函數。如果不需要這個對象,此處可以填null
。title
:新頁面的標題,但是所有瀏覽器目前都忽略這個值,因此這裏可以填null
。url
:新的網址,必須與當前頁面處在同一個域。瀏覽器的地址欄將顯示這個網址。
window的popstate事件
當活動歷史記錄條目更改時,將觸發popstate事件。如果被激活的歷史記錄條目是通過對history.pushState()的調用創建的,或者受到對history.replaceState()的調用的影響,popstate事件的state屬性包含歷史條目的狀態對象的副本。
需要注意的是調用history.pushState()或history.replaceState()不會觸發popstate事件。只有在做出瀏覽器動作時,纔會觸發該事件,如用戶點擊瀏覽器的回退按鈕(或者在Javascript代碼中調用history.back())
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<ul>
<li><a href="/">/</a></li>
<li><a href="/page1">page1</a></li>
<li><a href="/page2">page2</a></li>
</ul>
<div class="content-div"></div>
</body>
<script>
class RouterClass {
constructor(path) {
this.routes = {}; // 記錄路徑標識符對應的cb
history.replaceState({ path }, null, path); // 進入狀態
this.routes[path] && this.routes[path]();
window.addEventListener("popstate", e => {// 當用戶點擊瀏覽器的前進或者後退觸發
console.log(e.state)
const path = e.state && e.state.path;
this.routes[path] && this.routes[path]();
});
}
/* 初始化 */
static init() {
window.Router = new RouterClass(location.pathname);
}
/* 註冊路由和回調 */
route(path, cb) {
this.routes[path] = cb || function() {};
}
/* 跳轉路由,並觸發路由對應回調 */
go(path) {
history.pushState({ path }, null, path);
console.log(history);
this.routes[path] && this.routes[path]();
}
}
RouterClass.init();
const ul = document.querySelector("ul");
const ContentDom = document.querySelector(".content-div");
const changeContent = content => (ContentDom.innerHTML = content);
Router.route("/", () => changeContent("默認頁面"));
Router.route("/page1", () => changeContent("page1頁面"));
Router.route("/page2", () => changeContent("page2頁面"));
ul.addEventListener("click", e => {
console.log(e.target.tagName);
if (e.target.tagName === "A") {
e.preventDefault();
Router.go(e.target.getAttribute("href"));
}
});
</script>
</html>
Hash 模式和 History 模式對比
Hash 模式是使用 URL 的 Hash 來模擬一個完整的 URL,因此當 URL 改變的時候頁面並不會重載。History 模式則會直接改變 URL,所以在路由跳轉的時候會丟失一些地址信息,在刷新或直接訪問路由地址的時候會匹配不到靜態資源。因此需要在服務器上配置一些信息,讓服務器增加一個覆蓋所有情況的候選資源,比如跳轉 index.html 什麼的
hash路由 優缺點
-
優點
- 實現簡單,兼容性好(兼容到
ie8
) - 絕大多數前端框架均提供了給予
hash
的路由實現 - 不需要服務器端進行任何設置和開發
- 除了資源加載和
ajax
請求以外,不會發起其他請求
- 實現簡單,兼容性好(兼容到
-
缺點
- 對於部分需要重定向的操作,後端無法獲取
hash
部分內容,導致後臺無法取得url
中的數據,典型的例子就是微信公衆號的oauth
驗證 - 服務器端無法準確跟蹤前端路由信息
- 對於需要錨點功能的需求會與目前路由機制衝突
- 對於部分需要重定向的操作,後端無法獲取
History(browser)路由 優缺點
-
優點
- 對於重定向過程中不會丟失
url
中的參數。後端可以拿到這部分數據 - 絕大多數前段框架均提供了
browser
的路由實現 - 後端可以準確跟蹤路由信息
- 可以使用
history.state
來獲取當前url
對應的狀態信息
- 對於重定向過程中不會丟失
-
缺點
- 兼容性不如
hash
路由(只兼容到IE10
) - 需要後端支持,每次返回
html
文檔
- 兼容性不如
參考文章
實例來源:https://segmentfault.com/a/1190000018081475
官方文檔:https://developer.mozilla.org/en-US/docs/Web/API/History_API
優缺點比較:https://juejin.im/post/5b5ec5...