React-router v4.2下的 history

本文翻譯自[https://medium.com/@pshrmn/a-little-bit-of-history-f245306f48dd ]
本文為本人在學(xué)習(xí)過程中的翻譯,如有錯(cuò)誤希望指出以及諒解。

如果你想要理解React router,你首先要學(xué)習(xí)history,更明確的說(shuō)就是這個(gè)為React router提供了核心功能的history包。這使得客戶端的項(xiàng)目可以很容易的增加基于位置的導(dǎo)航,這對(duì)于單頁(yè)面應(yīng)用來(lái)說(shuō)是必不可少的。
npm install --save history
history有三種類型:browser,hash,memory。這個(gè)包分別提供了用于創(chuàng)建各個(gè)類型的歷史信息的方法。
import {createBrowserHistory, createHashHistory, createMemoryHistory} from 'history'
如果你使用了React router,它會(huì)自動(dòng)為你創(chuàng)建history對(duì)象,這樣你就不需要真正的去跟history打交道。但是,去理解各種類型的history還是很重要的,這樣你就能夠決定哪一種才是真正適合你的項(xiàng)目的。

什么是history

不管你創(chuàng)建了什么類型的history,你最終得到的對(duì)象都擁有著基本相同的屬性和方法。

Location

history對(duì)象中最重要的屬性就是'location'。location對(duì)象表名了你的應(yīng)用當(dāng)前位于什么位置。它包含了一些從URL中獲取到的屬性:pathname,search,hash。
除此之外,每個(gè)location對(duì)象中都一個(gè)與之相關(guān)的唯一的'key'屬性。這個(gè)'key'可以用于鑒別跟存儲(chǔ)特定location的相關(guān)數(shù)據(jù)。
最后,每個(gè)location對(duì)象都有一個(gè)與之相關(guān)的state,這提供了一種在location上添加一些信息,但是這些信息不會(huì)展示在URL上的方法。

{ 
    pathname: '/here', 
    search: '?key=value', 
    hash: '#extra-information', 
    state: {model: true}, 
    key: 'abc123'
}

一個(gè)history對(duì)象被創(chuàng)建的時(shí)候需要一個(gè)初始化的location值。不同類型的history的location的初始值的確定方式是不一樣的。比如:browser類型的history會(huì)先解析當(dāng)前URL值。

One location to rule them all?

盡管我們可以訪問的當(dāng)前路徑只有一個(gè),但是history對(duì)象保持著對(duì)一些路徑的跟蹤。正是這種能夠在location數(shù)組中添加,移除位置的能力讓history對(duì)象具有了記錄歷史的功能(讓其稱作‘history’),如果history對(duì)象只知道當(dāng)前路徑的相關(guān)信息,那它就更應(yīng)當(dāng)叫做‘present’(當(dāng)前)。
除了在對(duì)象中保存著一個(gè)路徑數(shù)組,history對(duì)象也維護(hù)著一個(gè)索引值,這個(gè)索引表示著當(dāng)前所在路徑在這個(gè)路徑數(shù)組中的位置。
對(duì)于memory 類型的history,這些都是被明確聲明的。對(duì)于browser跟hash類型的history,這個(gè)路徑信息的數(shù)組以及索引值都是由瀏覽器控制的并且不能被用戶直接訪問。

導(dǎo)航

一個(gè)只有l(wèi)ocation屬性的對(duì)象并不能讓我們產(chǎn)生很大興趣。一個(gè)history對(duì)象真正讓人產(chǎn)生興趣的地方在于它有一些導(dǎo)航的方法。

Push

Push方法可以讓你導(dǎo)航到一個(gè)新的地址。這個(gè)操作會(huì)在當(dāng)前的location數(shù)組之后添加一個(gè)新的地址。在這個(gè)方法操作之后,location數(shù)組中任何位于這個(gè)地址之后的地址(因?yàn)橛脩敉ㄟ^后退按鈕回到上一個(gè)頁(yè)面會(huì)在當(dāng)前地址之后產(chǎn)生一個(gè)地址)都會(huì)被清除。
默認(rèn)情況下,當(dāng)你點(diǎn)擊通過React router生成的<Link>的時(shí)候,調(diào)用的是history.push方法。
history.push({pathname: '/new-place'})

Replace

Replace方法跟push方法類似,但是不同的是Replace是在當(dāng)前索引的位置替換地址而不是在當(dāng)前索引的位置之后添加一個(gè)新的地址。位于當(dāng)前索引位置之后的地址不會(huì)被清楚。
對(duì)于重定向來(lái)說(shuō)使用replace這個(gè)方法是很好的,這也正是React router中的<Redirect>組件所采取的方法。
例如:假設(shè)你處在第一個(gè)頁(yè)面并且點(diǎn)擊了一個(gè)導(dǎo)航到第二個(gè)頁(yè)面的鏈接,但是這第二個(gè)頁(yè)面又會(huì)將你重定向到第三個(gè)頁(yè)面。如果這里的重定向使用的是push方法,那么當(dāng)你在第三個(gè)頁(yè)面點(diǎn)擊返回按鈕的時(shí)候會(huì)將你帶回第二個(gè)頁(yè)面(此時(shí)可能會(huì)再次將你又重定向會(huì)第三頁(yè)),相反,如果你使用replace方法,在第三頁(yè)執(zhí)行返回操作會(huì)將你帶回第一頁(yè)。
history.replace({pathname: '/go-here-instead'})
最后,還有三個(gè)相關(guān)的方法:goBackgoForwardgo
goBack方法返回到上一個(gè)頁(yè)面,同時(shí)也會(huì)將locations中數(shù)組的索引減少一個(gè)。
history.goBack()
goForward方法與goBack方法相反,它會(huì)向前前進(jìn)一個(gè)頁(yè)面,但是這只會(huì)發(fā)生在有有可以前進(jìn)的頁(yè)面的時(shí)候,就是當(dāng)用戶在這之前點(diǎn)擊過返回按鈕。
history.goForward()
gogoBackgoForward兩者有力的結(jié)合。當(dāng)向這個(gè)方法中傳遞的參數(shù)為負(fù)值時(shí),會(huì)依據(jù)location數(shù)組的位置信息后退到相應(yīng)的頁(yè)面,當(dāng)傳遞的參數(shù)為正值時(shí),會(huì)前進(jìn)相應(yīng)的頁(yè)面。
history.go(-3)

Listen!

History使用觀察者模式來(lái)在地址發(fā)生改變的時(shí)候來(lái)通知外部的代碼。
每個(gè)history對(duì)象都有一個(gè)listen方法,這個(gè)方法接收一個(gè)函數(shù)作為它的參數(shù)。這個(gè)函數(shù)會(huì)被添加到history中的用于保存監(jiān)聽函數(shù)的數(shù)組中。任何時(shí)候當(dāng)?shù)刂钒l(fā)生改變時(shí)(無(wú)論是通過在代碼中調(diào)用history對(duì)象的方法還是通過點(diǎn)擊瀏覽器的按鈕),history對(duì)象會(huì)調(diào)用它所有的監(jiān)聽函數(shù)。這使得你能夠編寫一些代碼用于監(jiān)聽當(dāng)?shù)刂钒l(fā)生改變時(shí),來(lái)執(zhí)行相關(guān)的操作。

const youAreHere = document.getElementById('youAreHere')
history.listen(function (location) {
  youAreHere.textContent = location.pathname
})

React Router的router組件會(huì)訂閱它的history對(duì)象,這樣它才能夠在地址發(fā)生改變時(shí)重新渲染加載。

Linking things together

每個(gè)history也都有一個(gè)createHref方法,這個(gè)方法接收一個(gè)location對(duì)象作為參數(shù),然后輸出一個(gè)URL。
在內(nèi)部history可以使用location對(duì)象來(lái)進(jìn)行導(dǎo)航。但是,對(duì)于那些不是來(lái)自于history包中的元素,比如:<a>,它是不知道location對(duì)象是什么的。所以為了能夠生成那些不知道location對(duì)象但是依舊能夠正確的進(jìn)行導(dǎo)航的HTML的標(biāo)簽,我們就必須能夠生成真正的URLs。

consot 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>

以上已經(jīng)包含了history的基本的API,雖然history還有好多別的屬性跟方法,但是上述的一些屬性跟方法已經(jīng)足夠我們來(lái)理解以及使用history對(duì)象了。

With our powers combined

你在確定出最適合你的項(xiàng)目的history類型的時(shí)候需要清楚這些history類型之間的區(qū)別。下面我們會(huì)一次闡述這三種類型的history的實(shí)例。

In the browser

browser跟hash類型的history都是在瀏覽器端進(jìn)行使用的。它們是在跟web API中的history跟location進(jìn)行交互的,所以當(dāng)前的location跟瀏覽地址欄中顯示的是一樣的。

const browserHistory = createBrowserHistory()
const hashHistory = createHashHistory()

這兩者之間的區(qū)別在于他們根據(jù)URL中的信息來(lái)創(chuàng)建location對(duì)象方法的不同。browser類型的history使用了完整的URL地址信息,但是hash類型只使用了URL地址中位于第一個(gè)'#'標(biāo)志之后的部分。

// Given the following URL
url = 'http://www.example.com/this/is/the/path?key=value#hash'
// a browser history creates the location object:
{
  pathname: '/this/is/the/path',
  search: '?key=value',
  hash: '#hash'
}
// a hash history creates the location object:
{
  pathname: 'hash',
  search: '',
  hash: ''
}
Hashing things out

你問什么要用hash類型的history呢?因?yàn)槔碚撋袭?dāng)你訪問一個(gè)地址的時(shí)候在你的服務(wù)器上通常都會(huì)有一個(gè)與之相對(duì)應(yīng)的文件。
但是對(duì)于動(dòng)態(tài)服務(wù)器來(lái)說(shuō),被請(qǐng)求的內(nèi)容并非都要真正存在于服務(wù)器上。取而代之的是,服務(wù)器根據(jù)請(qǐng)求的地址來(lái)動(dòng)態(tài)決定并生成相應(yīng)的HTML信息。
但是,對(duì)于靜態(tài)服務(wù)器來(lái)說(shuō)它只能返回真正存在于服務(wù)器上的文件。它所能做的最具有動(dòng)態(tài)化的就是當(dāng)URL地址只指定了目錄信息的時(shí)候返回index.html
考慮到靜態(tài)服務(wù)器的一些限制,最簡(jiǎn)單的解決方案就是從你的服務(wù)器獲取HTML的‘真實(shí)’location只有一個(gè):就是你的應(yīng)用只有一個(gè)URL,但是這違背了使用history的初衷。為了避免這個(gè)限制,hash類型的history使用了URL中的hash部分來(lái)設(shè)置以及訪問位置信息。

// If example.com uses a static file server, these URLs would
// both fetch html from /my-site/index.html
http://www.example.com/my-site#/one
http://www.example.com/my-site#/two
// However, with a hash history, an application's location will be
// different for each URL because the location is derived from
// the hash section of the URL
{ pathname: '/one' }
{ pathname: '/two' }

雖然hash模式的history工作的很好,但是這會(huì)有被黑客入侵的風(fēng)險(xiǎn),因?yàn)檫@個(gè)模式嚴(yán)重依賴于將所有的路徑信息存儲(chǔ)在URL的hash中。所以,只有在你的網(wǎng)站沒有動(dòng)態(tài)服務(wù)器可以用來(lái)提供HTML的時(shí)候采取采用hash模式的history。

Memory: The Catch-all History

關(guān)于memory location的最好的地方在于只要可以運(yùn)行JavaScript你就可以使用它。
一個(gè)最簡(jiǎn)單的例子就是你可以在Node環(huán)境下運(yùn)行的單元測(cè)試中使用它。這樣你就可以測(cè)試那些依賴于history對(duì)象的代碼而不需要真正在瀏覽器中測(cè)試。
更重要的是你還可以在移動(dòng)App里面使用memory類型的history。你可以在react-native類型的App中通過react-router-native來(lái)使用memory類型的history來(lái)允許使用基于location的導(dǎo)航。
如果你真的想用,你甚至可以在瀏覽器中使用memory history(這樣你就用不到瀏覽器的地址欄了)
memory history跟browse historyr以及hash history最大的區(qū)別在于:memory history在內(nèi)存中保存著自己的location數(shù)組。在創(chuàng)建memory history的時(shí)候你可以傳入一些信息用于設(shè)置初始狀態(tài)。這個(gè)狀態(tài)包括:保存在數(shù)組中的位置信息以及當(dāng)前位置在這個(gè)數(shù)組中的索引。這區(qū)別于browser
history以及hash history的依靠瀏覽器來(lái)存儲(chǔ)相應(yīng)的數(shù)組。

const history = createMemoryHistory({
  initialEntries: ['/', '/next', '/last'],
  initialIndex: 0
})

無(wú)論你最終選擇了什么類型的history,你最終都可以很容易并且高效的實(shí)現(xiàn)導(dǎo)航以及以及位置的加載渲染。

Notes

[1].search屬性的值是字符串形式的而不是解析過的對(duì)象。這是因?yàn)樵谟行┣闆r下這些字符串只有一點(diǎn)點(diǎn)不同,然而這就需要需要用不同的字符串的解析包來(lái)解析這些字符串。正因如此,history讓你自己來(lái)決定如何解析這些字符串而不是給你一個(gè)解析字符串的方法。如果你正在尋找解析字符串的方法,這里有一些流行的選擇:'qs','query-string','querystring'以及原生的'URLSearchParams'。
[2].限制就是缺少安全性。瀏覽器的歷史的location數(shù)組中不僅僅包含了我們已經(jīng)訪問過的地址的信息。允許了對(duì)這個(gè)數(shù)組的訪問就會(huì)泄露用戶的瀏覽歷史的信息,這不是一個(gè)網(wǎng)站應(yīng)該被允許做的。
[3].默認(rèn)情況下,browser history里面會(huì)創(chuàng)建一個(gè)location對(duì)象,這個(gè)對(duì)象里面的pathname屬性保存著URL的完整路徑。但是你可以自己為history指定一個(gè)生成的名稱,這樣完整路徑中的一部分就會(huì)被完全忽略掉。

const history = createBrowserHistory({ basename: '/path' })
// given the url: [http://www.example.com/path/here](http://www.example.com/path/here)
// the history object will create the location
{ pathname: '/here', ... }

[4].理論上來(lái)說(shuō),在你的應(yīng)用中你可以針對(duì)每個(gè)有效的URL都返回同一個(gè)HTML文件。如果你的URL都是靜態(tài)的,那這可以運(yùn)行,但是這樣會(huì)創(chuàng)建許多多余的文件。如果你在URL中使用參數(shù)來(lái)匹配大量的可能值,這種方案是不可行的。
[5].如果你在使用memory history的時(shí)候沒有給它提供初始的location以及index值,那它會(huì)自己生成默認(rèn)值:

entries = [{ pathname: '/' }]
index = 0

這對(duì)大多數(shù)應(yīng)用來(lái)說(shuō)足夠了,但是對(duì)于session自動(dòng)恢復(fù)來(lái)說(shuō)為history來(lái)預(yù)先設(shè)置初始值是非常有用的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容