原文html
若是你想理解React Router,那麼應該先理解history。更確切地說,是history這個爲React Router提供核心功能的包。它能輕鬆地在客戶端爲項目添加基於location的導航,這種對於單頁應用相當重要的功能。react
npm install --save history
存在三類history,分別時browser,hash,與 memory。history包提供每種history的建立方法。git
import { createBrowserHistory, createHashHistory, createMemoryHistory } from 'history'
若是你使用React Router,他會爲你自動建立history對象,因此你並不須要與history進行直接的交互。不過,理解不一樣類型的history依舊很重要,這樣你能在項目中決定到底是用哪一個。github
不管你建立哪一種history,你最終都會獲得一個幾乎擁有相同屬性與方法的對象。web
history對象中最重要的屬性就是location。location對象反映了當前應用所在的"位置"。其包含了pathname
,search
[注1],hash
這種由'URL'派生出的屬性。npm
此外,每個location都擁有一個與之關聯且獨一無二的key
。'key'用於特定location的識別,向特定location存儲數據。react-native
最後,location能夠擁有與之相關的狀態。這是一些固定的數據,而且不存在於URL之中。數組
{ pathname: '/here', search: '?key=value', hash: '#extra-information', state: { modal: true }, key: 'abc123' }
當建立一個history對象後,須要初始化location。對於不一樣類型history這一過程也不相同。例如,browser history會解析當前URL。瀏覽器
誠然咱們只能訪問當前location,history對象持續追蹤着一組location。正由於擁有添加location並可以訪問數組中任意location的能力,history才能被稱爲「歷史」。若是history只能記錄當前location,那就應該叫它「present」。緩存
除了一組location外,history也保存一個索引值,用來指向當前所對應的location。
對於memory history,它們被直接定義。而對於browser history與hash history,數組與索引被瀏覽器所控制,並不能直接訪問[注2]。
能夠說navigation方法是擁有location屬性的history體系的點睛之筆。navigation容許你改變當前location。
push
方法使能你跳轉到新的location。經過在當前location後添加新的location時,任意的'將來'location會被清除(以前由後退按鈕而造成的在當前location後的location)。
默認狀況下,當你點擊<Link>
時,會調用history.push方法進行導航。
history.push({ pathname: '/new-place' })
replace
方法與push
類似,但它並不是添加location,而是替換當前索引上的位置。'將來'location將不會被清除。
重定向時要使用replace
。這也是React Router的<Redirect>組件中使用的方法。
例如,當你在頁面1經過點擊link按鈕導航到頁面2,頁面2可能會重定向到頁面3。若是使用push
方法,點擊放回按鈕將從頁面3返回到頁面2(這裏有潛在的可能再重定向到頁面3)。若是使用replace
方法,會從頁面三直接返回頁面1。
history.replace({ pathname: '/go-here-instead' })
最後有三個帶‘go’的方法,它們分別是goBack
,goForward
與go
。goBack
返回一層頁面。其實是將history的索引值減1。
history.goBack()
goForward
與goBack
相對。向前一層頁面。這僅在擁有'將來'location生效,即當用戶點擊了後退按鈕。
history.goForward()
go
是一個強大的方法,幷包含了goForward
與goBack
的功能。傳入負數則退後,傳入正數則向前。
history.go(-3)
採用觀察者模式,在location改變時,history會發出通知。每個history對象都有listen方法,接受一個函數做爲參數。這個函數會被添加到history儲存的監聽函數數組中。當location變化時(如代碼調用history方法或用戶點擊瀏覽器按鈕),history對象將會調用全部listener方法。這能讓你在location變化時來設置代碼更新。
const youAreHere = document.getElementById('youAreHere') history.listen(function(location) { youAreHere.textContent = location.pathname })
React Router的router
組件將會訂閱history對象,這樣當location變化時,其能從新渲染。
每一類history都擁有createHref
方法,其使用location對象,輸出URL。
內部,history經過location對象進行導航。然而像錨點元素(a),它並不知道history
這個包,也不知道location對象是什麼。爲了能讓生成的HTML
在不須要history的狀況下,依舊可以導航。咱們必須生成真的URL。
const location = { pathname: '/one-fish', search: '?two=fish', hash: '#red-fish-blue-fish' } const url = history.createHref(location) const link = document.createElement('a') a.href = url // <a href='/one-fish?two=fish#red-fish-blue-fish'></a>
以上涵蓋了基礎的history
API。雖然還有其餘未介紹的屬性與方法,但上述方法可以是你明白history對象是如何運做的。
不一樣類型的history間仍是存在差別的,這須要你去考慮選擇一個適合你項目的history。
Between the three of them, any use case should be covered.
browser history與hash history都被用於瀏覽器環境。它們與history和location的web API進行交互,所以當前location與瀏覽器地址欄中展現的是相同的。
const browserHistory = createBrowserHistory() const hashHistory = createHashHistory()
它們二者的最大區別在於從URL建立location的方式。browser history使用完整URL[注3],而hash history只使用在第一個hash後的那部分URL。
// 提供以下URL url = 'http://www.example.com/this/is/the/path?key=value#hash' // browser history建立的location對象: { pathname: '/this/is/the/path', search: '?key=value', hash: '#hash' } //hash history建立的location對象: { pathname: 'hash', search: '', hash: '' }
爲什麼你須要hash history?理論上來講當你導航到一個URL時,服務端必有一個相應文件與之對應。對於動態服務,請求文件並不須要真實存在。相反,服務端會檢查請求的URL並決定返回的HTML。
然而,靜態文件服務能夠直接返回存在磁盤中的文件。靜態服務能作的最動態的事就是當URL制定目錄時,從目錄中返回index.html
文件。
因爲靜態文件服務的這種限制,最簡單的解決方案[注4]就是在服務端僅使用一個真實的location來返回用戶端的獲取需求。固然,僅有一個location意味着你的應用只有一個URL,這樣就沒法使用history。爲了解決這一問題,hash history使用使用URL的哈希部分來讀寫location。
// 若是 example.com 使用靜態資源服務, 這三個URL都將從 // /my-site/index.html獲取相同數據 http://www.example.com/my-site#/one http://www.example.com/my-site#/two // 然而因爲使用hash history,應用中三者的location是不一樣的, // 由於location取決於URL的哈希部分 { pathname: '/one' } { pathname: '/two' }
縱然hash history運做良好,但因爲其依賴將全部路徑信息存在URL的哈希中,它被認爲有可能遭到黑客攻擊。所以當網站沒有動態服務時再考慮使用它吧。
使用memory location最棒的體驗就是你能夠在能使用JavaScript的地方隨意使用。
一個簡單的例子你能夠經過運行Node在單元測試中使用它。這容許你能在不依賴瀏覽器運行的狀況下測試代碼。
更牛逼的是,memory history能夠被使用在app中。在react-native
app中react-router-native
使用memory history來實現基於location的導航。
你能夠在瀏覽器中使用使用memory history,若是你願意的話。(雖然這樣你會失去與地址欄的交互能力)。
這memory history與其餘兩類history最大的區別在於其維護着本身的location。當建立memory history後你能夠傳入信息進行初始化狀態。這個狀態是一個location數組與當前location的索引[注5]。這與其餘兩類history是不一樣的,它們依賴瀏覽器來存儲這個location數組。
const history = createMemoryHistory({ initialEntries: ['/', '/next', '/last'], initialIndex: 0 })
使用history代替你來處理哪些相對繁瑣且易錯的是一個行之有效的方法。
不管你選擇了何種類型的history,他們都是極易使用,而且擁有強大的能力進行導航與基於loaction的渲染。
[1] search
屬性是一個字符串而非被解析對象。因爲大多數字符串解析包在使用上各不相同。因此history把選擇權留給了開發者而不是強制使用某種字符串解析包。若是你想了解更多,這裏推薦一些流行的:query-string,querystring與原生的URLSearchParams
[2] 這是出於安全性的限制。在瀏覽器中history的location數組不只包涵了訪問過的location信息。若是開放瀏覽會泄漏使用者的瀏覽器歷史信息,所以沒法開放訪問。
[3] m默認狀況下,browser history建立的location對象,它的路徑名是URL全路徑名。固然,你能夠爲history定一個基礎名,這樣路徑名中的這部分將會被忽略。
const history = createBrowserHistory({ basename: '/path' }) // 給出的路徑 url: http://www.example.com/path/here // history對象將會建立以下location { pathname: '/here', ... }
[4] 理論上,可讓應用中的每一個有效URL返回相同的HTML文件。雖然這能夠事項,但若是全部的URL都是靜態的,會產生大量冗餘文件。不過任意地址都使用參數大量來匹配大量可能址是不可行的。
[5] 若是並未提供memory history的初始化location數組與索引,則會生成以下默認值:
entries = [{ pathname: '/' }] index = 0
對於大部分應用這已經足夠好了,但提早寫入history對於恢復內容仍是一個很是有用的方法。