React-Router的特性
路由的基本原理就是保证view和url同步,React-Router有下面一些特点
-
声明式的路由 跟react一样,我们可以声明式的书写router,可以用JSX语法
-
嵌套路由及路径匹配
-
支持多种路由切换方式 可以用hashchange或者history.putState。
hashChange的兼容性较好,但在浏览器地址栏显示#看上去会很丑;而且hash记录导航历史不支持location.key和location.state。hashHistory即hashChange的实现。
history.putState可以给我们提供优雅的url,但需要额外的服务端配置解决路径刷新问题;browserHistory即history.pushState的实现。
因为两种方式都有优缺点,我们可以根据自己的业务需求进行挑选,这也是为什么我们的路由配置中需要从react.router引入browserHistory并将其当作props传给Router。
React Router的API:
主要有
-
BrowserRouter
-
HashRouter
-
MemoryRouter
-
StaticRouter
-
Link
-
NavLink
-
Redirect
-
Prompt
-
Route
-
Router
-
Switch
这些组件的具体用法可以在官网查看,(reacttraining.com/react-route…)(segmentfault.com/a/119000001…),这里对它们做个总结:
BrowserRouter使用HTML5提供的History api(putState,replaceState和popState事件)来保持UI和URL的同步,而HashRouter使用URL的hash部分(即window.location.hash)来保持UI和URL的同步;HashRouter主要用于支持低版本的浏览器,因此对于一些新式浏览器,我们鼓励使用BrowserHistory。
MemoryRouter将历史记录保存在内存中,这个在测试和非浏览器环境中很有用,例如react native。
StaticRouter是一个永远不会改变位置的Router,这在服务端渲染场景中非常有用,因为用户实际上没有点击,所以位置时间上没有发生变化。
NavLink与Link的区别主要在于,前者会在与URL匹配时为呈现要素添加样式属性。
Route是这些组件中重要的组件,它的任务就是在其path属性与某个location匹配时呈现一些UI。
对于Router,一般程序只会使用其中一个高阶Router,包括BrowserRouter,HashRouter,MemoryRouter,NativeRouter和StaticRouter;
Switch用于渲染与路径匹配的第一个Route或Redirect。
-
React-Router的依赖基础history
1.基础概念
history是一个独立的第三方js库,可以用来兼容在不同浏览器、不同环境下对历史记录的管理。主要包括三类,
- 老版本的history:通过hash来实现,对应createHashHistory
- 高版本的history:通过HTML5里面的history,对应createBrowserHistory
- node环境下:主要存储在memory里面,对应creatememoryHistory
上面针对不同的环境提供了三个API,但三个API有一些共性的操作,将其抽象了一个公共的文件createHistory:
// 内部的抽象实现
function createHistory(options={}) {
...
return {
listenBefore, // 内部的hook机制,可以在location发生变化前执行某些行为,AOP的实现
listen, // location发生改变时触发回调
transitionTo, // 执行location的改变
push, // 改变location
replace,
go,
goBack,
goForward,
createKey, // 创建location的key,用于唯一标示该location,是随机生成的
createPath,
createHref,
createLocation, // 创建location
}
}
复制代码
其中需要注意的是,此时的location跟浏览器中原生的location是不想同的,最大的区别就在于里面多了key字段,history内部是通过key来进行location的操作。
function createLocation() {
return {
pathname, // url的基本路径
search, // 查询字段
hash, // url中的hash值
state, // url对应的state字段
action, // 分为 push、replace、pop三种
key // 生成方法为: Math.random().toString(36).substr(2, length)
}
}
复制代码
2.内部解析
三个API的大致的技术实现如下:
-
createBrowserHistory
: 利用HTML5里面的history -
createHashHistory
: 通过hash来存储在不同状态下的history信息 -
createMemoryHistory
: 在内存中进行历史记录的存储
2.1 执行url前进:
-
createBrowserHistory
: pushState、replaceState -
createHashHistory
:location.hash=***
location.replace()
-
createMemoryHistory
: 在内存中进行历史记录的存储
伪代码实现如下:
// createBrowserHistory(HTML5)中的前进实现
function finishTransition(location) {
...
const historyState = { key };
...
if (location.action === 'PUSH') ) {
window.history.pushState(historyState, null, path);
} else {
window.history.replaceState(historyState, null, path)
}
}
// createHashHistory的内部实现
function finishTransition(location) {
...
if (location.action === 'PUSH') ) {
window.location.hash = path;
} else {
window.location.replace(
window.location.pathname + window.location.search + '#' + path
);
}
}
// createMemoryHistory的内部实现
entries = [];
function finishTransition(location) {
...
switch (location.action) {
case 'PUSH':
entries.push(location);
break;
case 'REPLACE':
entries[current] = location;
break;
}
}
复制代码
2.2 检测url回退:
-
createBrowserHistory
:popstate
-
createHashHistory
:hashchange
-
createMemoryHistory
: 因为是在内存中操作,跟浏览器没有关系,不涉及UI层面的事情,所以可以直接进行历史信息的回退
伪代码实现如下:
// createBrowserHistory(HTML5)中的后退检测
function startPopStateListener({ transitionTo }) {
function popStateListener(event) {
...
transitionTo( getCurrentLocation(event.state) );
}
addEventListener(window, 'popstate', popStateListener);
...
}
// createHashHistory的后退检测
function startPopStateListener({ transitionTo }) {
function hashChangeListener(event) {
...
transitionTo( getCurrentLocation(event.state) );
}
addEventListener(window, 'hashchange', hashChangeListener);
...
}
// createMemoryHistory的内部实现
function go(n) {
if (n) {
...
current += n;
const currentLocation = getCurrentLocation();
// change action to POP
history.transitionTo({ ...currentLocation, action: POP });
}
}
复制代码
2.3 存储state
将state存储在sessionStorage里面
// createBrowserHistory/createHashHistory中state的存储
function saveState(key, state) {
...
window.sessionStorage.setItem(createKey(key), JSON.stringify(state));
}
function readState(key) {
...
json = window.sessionStorage.getItem(createKey(key));
return JSON.parse(json);
}
// createMemoryHistory仅仅在内存中,所以操作比较简单
const storage = createStateStorage(entries); // storage = {entry.key: entry.state}
function saveState(key, state) {
storage[key] = state
}
function readState(key) {
return storage[key]
}
复制代码
从上面三个例子可以看出,三种api的实现方式是不一样的,具体的细节可以看源码。
React—Router的原理
React-Router是在history库基础上实现的。
![0e753ac7e440fca9cb1cd7995ce26ef0_b](C:\Users\cyw\Desktop\summary\理解React Router路由\图片\0e753ac7e440fca9cb1cd7995ce26ef0_b.png)
点击Link标签路由系统发生的变化
-
Link组件最终会被渲染成HTML标签,它的to,query,hash属性会被组合在一起并渲染为href属性
location = {
pathname, // 当前路径,即 Link 中的 to 属性
search, // search
hash, // hash
state, // state 对象
action, // location 类型,在点击 Link 时为 PUSH,浏览器前进后退时为 POP,调用 replaceState 方法时为 REPLACE
key, // 用于操作 sessionStorage 存取 state 对象
};
复制代码
将上述location对象作为参数传入到Transition方法中,得到新的location对象
在得到新的location对象后,系统内部的matchRoutes方法会匹配出Route组件树中与与当前location对象匹配的一个子集,并且得到了nextState
nextState = {
location, // 当前的 location 对象
routes, // 与 location 对象匹配的 Route 树的子集,是一个数组
params, // 传入的 param,即 URL 中的参数
components, // routes 中每个元素对应的组件,同样是数组
};
复制代码
点击前进或后退发生的变化
可以简单把浏览器的历史记录比作成一个仅有入栈操作的栈,当用户浏览到某一个页面时将该文档存入到栈中,点击后退或前进按钮时移动指针到history栈中对应的某一个文档。
使用hashChange事件来监听window.location.hash的变化,路由系统会将所有的路由信息都保存到location.hash中 hash变化时浏览器会更新url,并且在history栈中产生一条记录 window.addListener('hashChange',listen,false)事件监听器可以通过hash fragment获取当前url对应的location对象 接下来的过程与点击Link组件时一致state对象可以存储一些恢复该页面所需要的简单信息,上文已经提到state会作为属性存储在location对象中,我们可以通过location.state来获取
在react-router内部将该对象存储到sessionStorage中,即saveState操作