天天看點

前端路由與曆史

在做全棧支援的過程中發現很多後端同學對hashHistory和browserHistory的差別不清楚不了解,也不知道如何正确地配置browserHistory,在本文中我們希望回答大家的這些疑惑:

  1. React應用url中的?_k=xxx 是什麼?
  2. history是什麼?為什麼我們需要和它打交道?
  3. hashHistory和browserHistory有什麼差別? 什麼情況下使用hashHistory,什麼情況下使用browserHistory?
  4. 如何在SOFA系統中配置browserHistory?

前言

剛學習React那會一直很疑惑為什麼React應用的url後面非得有個奇怪的?_k=xxx,比方說現在linke的連結就是這樣的:

https://linke.alipay.com/index.htm#/alipay?_k=9v1xhg      

而通常我們使用的連結都是類似語雀連結的形式,比較幹淨清爽:

https://lark.alipay.com/chenglin.mcl/forget/laub8f      

仔細看這個連結會發現連結中還有個#字元号,而井号前面是index.htm 似乎已經把網頁加載完了?那#後面的是什麼?_k字段又有什麼意義?

一、React-router

這個_k字段是我們在使用前端路由庫的時候産生的,#後面的内容叫做hash。在用React全家桶的時候我們會接觸到​​React-router​​這個庫,我這是React官方出品的前端路由解決方案,在使用的時候會讓我們傳入history對象到路由元件中:

import { browserHistory } from 'react-router'

render(
  <Router history={browserHistory} routes={routes} />,
  document.getElementById('app')
)      

這個history的取值通常有如下幾種:

● browserHistory

● hashHistory

● createMemoryHistory

browserHistory是官方推薦的history,但需要進行一定的配置才可以使用,在開發階段我們通常都是使用的第二種hashHistory,用了hashHistory就會發現連結中出現了#号,而且有奇怪的字段_k。而createMemoryHistory用的比較少這裡不多描述。

是以history是什麼?hash是什麼意思?hashHistory為什麼會有上面的效果?

二、路由

要講history我們首先得聊聊路由。簡單來說路由就是用來跟後端伺服器進行互動的一種方式,通過不同的路徑,來請求不同的資源,請求不同的頁面是路由的其中一種功能。路由這個概念一開始是來自于後端,比方說通路www.example.com/test/index.html,伺服器會根據路由配置傳回相應的頁面資訊。

前端路由與曆史

這仍然是目前大多數網站所采取的方式,比如在SOFA MVC當中的VM模闆也是同樣吐整個頁面出來到浏覽器,這樣路由變化的時候整個頁面都會重新整理,導緻資源的浪費,使用者需要花費額外的時間等待頁面重新整理,造成體驗不佳。

三、異步互動方式

而打破這個局面的是ajax的誕生,ajax允許頁面局部重新整理,能夠快速地将漸步更新呈現在使用者界面上,不需要重載整個頁面,這使得程式能夠更快地回應使用者的操作。首先讓Ajax技術發揚光大的是​​Google的Gmail​​,這種異步擷取資料局部重新整理的能力讓Gmail獲得很大的成功,網頁應用獲得了近似桌面應用的使用體驗,這奠定了後面異步互動方式繁榮的基礎,開發人員首次意識到我去還可以這樣玩。

這種異步互動的集大成者就是SPA,在單頁應用中,不僅頁面互動不需要重新整理頁面,連頁面跳轉也不需要重新整理頁面,而實作頁面跳轉不需要重新整理頁面的技術方案就是前端路由。

前端路由與曆史

前端路由的實作方式本質上是檢測 url 的變化,截獲 url 位址,然後解析比對路由規則。那麼有沒有什麼方式可以讓我們改變url而不導緻頁面向背景發送請求呢?

一種解決方式是通過hash,另一種方式是利用浏覽器自帶的history對象。

四、Hash

那麼什麼是hash呢?hash是URL中的錨點,是​

​#​

​後面的部分,例如www.example.com/index.html#footer中這個#footer就是hash串,你可以在浏覽器的開發者工具中通過window.location.hash擷取頁面url的hash值。

這個錨點的作用是在不請求伺服器的情況下定位到頁面的某個位置,如用id辨別的位置,比如上面的footer可以讓我們在通路頁面時自動滾動到頁面底部。而這個hash有個特性是在在第一個#後面出現的字元串都會被認為是位置字元,而不會被發送到伺服器端,是以用改變#後面的值不會觸發頁面的重載,比如在浏覽器控制台中輸入location.hash=‘123’ 會讓url帶上#123但頁面沒有重新重新整理。而且每一次改變#後的部分,都會在浏覽器的通路曆史中增加一個記錄,這對ajax應用非常有用。

利用hash的特性可以在不觸發頁面重新整理的情況下,解析目前hash的值和前端設定的路由進行比對。比如#introduction,比對上之後擷取介紹頁的内容挂載到預設的節點上。

在hashHistory的實作方式中_k存儲的是一個hash的key,用于快速索引記錄在記憶體中的history對象。

五、認識history對象

MDN: History 接口允許操作浏覽器的曾經在标簽頁或者架構裡通路的會話曆史記錄

在使用浏覽器浏覽網頁的過程中,你很自然地想要回到上一頁或者到下一頁去。當我們在不同的位址之間跳轉的時候就形成了我們的浏覽曆史,為了實作前進後退的功能,浏覽器産商定義了history對象,最初的history對象實作了go,forward,back等api來在曆史記錄見進行跳轉。比如你可以右擊打開浏覽器的console,輸入history.back() 看是否浏覽器的連結向後退了一步并重新整理了頁面。

在HTML5規範中,W3C對history對象進行了一波更新,新增了history.pushState()和history.replaceState()兩個方法,利用這2個方法,可以實作通過腳本修改浏覽器中的URL位址,而不觸發頁面重載。而實際上React router的browserHistory的命名就是類似于“使用浏覽器(browser)的api實作的history”

六、react-router 中 hashHistory 和 browserHistory 模式差別

hashHistory: 不需要伺服器配置,在 URL 生成一個 hash 來跟蹤狀态,通常在測試環境使用,也可以作為釋出環境使用。如果設為hashHistory,路由将通過URL的hash部分(#)切換,URL的形式類似example.com/#/some/path。

browserHistory: 需要伺服器端做配置,路徑是真實的URL,是 react-router 官方推薦首選。如果設為browserHistory,浏覽器的路由就不再通過Hash完成了,而顯示正常的路徑example.com/some/path,背後調用的是浏覽器的History API。

大多數情況下,browserHistory 模式明顯是優于 hashHistory 模式的,但 browserHistory模式 需要在伺服器上進行一定的配置,否則使用者直接向伺服器請求某個子路由,會顯示網頁找不到的404錯誤。

七、browserHistory模式為什麼需要配置伺服器?

在browserHistory 模式下,URL 是指向真實 URL 的資源路徑,當通過真實 URL 通路網站的時候(首頁),這個時候可以正常加載我們的網站資源,而使用者在非首頁下手動重新整理網頁時,由于路徑是指向伺服器的真實路徑,但該路徑下并沒有相關資源,使用者通路的資源不存在,傳回給使用者的是 404 錯誤。

是以要讓頁面正常運作,我們需要讓頁面在通路這些url的時候傳回單頁應用的index.html,然後再從前端解析到正确的路由進行加載。

八、browserHistory模式如何配置伺服器?

其本質是讓所有期望能正常通路的 url 都傳回 index.html,浏覽器上的path,會自動被React-router處理,進行無重新整理跳轉
  1. 比如如果開發伺服器使用的是webpack-dev-server,加上–history-api-fallback參數就可以了。
$ webpack-dev-server --inline --content-base . --history-api-fallback      
  1. 當伺服器為nginx時,*.conf配置類似如下:
server {
  ...
  location / {
    try_files $uri /index.html
  }
}      
  1. 針對于其他伺服器配置,原理都是一樣的,即把路由都指向index.html,靜态資源指向真實的檔案夾。比方說在SOFA應用中,我們可以配置 Controller 使得請求能夠被指向 建構出來的 HTML 頁面:
// IndexController.java

@RequestMapping(value = {"/*"}, method = RequestMethod.GET)
public String index(ModelMap modelMap) {
  return "index.html";  
}      

總結