原文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对于恢复内容仍是一个很是有用的方法。