React-router 依赖的History模块之createHashHistory源码阅读

之前就有发过一篇前端路由的实现的原理,但仅仅只是介绍前端路由所依赖的事件,在React-router中前端路由到底具体是如何实现并没有做过多介绍。刚好今天把react-router(版本4.0)源码从github上下载阅读,这里也做个整理。

npm install

从github上下载下来的react-router里,包含一个packages文件夹,里面包含了react-router,react-router-dom,react-router-native,react-router-redux,react-router-config五个子文件夹
其中react-router子文件夹是React Router的核心包

进入react-router子文件夹,全局搜索’hashchange’,却发现整个文件夹里面的代码中并没有包含hashchange,这说明真正实现前端路由的逻辑的代码并不在react-router文件夹的代码中,肯定就是包含在react-router所引用的模块中

查看react-router中package.json文件,可以看到react-router还依赖’history’模块。整个react-router就是基于history模块提供的API进行开发的
执行如下命令:

npm install

全部模块安装完毕后,进入node_modules/history/es.
可以看到History模块生成createHashHistory.js createBrowserHistory.js createMemoryHistory.js三个文件

这里我们先只查看createHashHistory.js,找找react-router的HashHistory到底是怎么生成的

源码阅读

这里直接查看主线逻辑上的代码,次要的支线代码直接忽略

如何获取hash

var getHashPath = function getHashPath() {
// We can't use window.location.hash here because it's not
// consistent across browsers - Firefox will pre-decode it!
var href = window.location.href;
var hashIndex = href.indexOf('#');
return hashIndex === -1 ? '' : href.substring(hashIndex + 1);
};

可以看到为了浏览器兼容性,这里没有使用window.location.hash
这里查看了location.hash在mdn上的文档,下面也有介绍这个API接口将会有在浏览器上移植到其他属性

如何修改URL中的Hash

var replaceHashPath = function replaceHashPath(path) {
var hashIndex = window.location.href.indexOf('#');
window.location.replace(window.location.href.slice(0, hashIndex >= 0 ? hashIndex : 0) + '#' + path);
};

Location.replace()方法以给定的URL来替换当前的资源。 与assign() 方法 不同的是调用replace()方法后,当前页面不会保存到会话历史中(session History),这样用户点击回退按钮将不会再跳转到该页面。

HashHistory处理逻辑

整个HashHistory的处理逻辑都集中函数createHashHistory中,代码结构如下:(这里省略了部分次要的代码)
默认返回一个history对象。

var createHashHistory = function createHashHistory() {
//.....
var handleHashChange = function handleHashChange() {
//.....
handlePop()
};
var handlePop = function handlePop(location) {
//.....
transitionManager.confirmTransitionTo(location, action, getUserConfirmation, function (ok) {
if (ok) {
// 处理跳转动作,更新返回的history对象
setState({ action: action, location: location });
} else {
// 处理返回动作的函数
revertPop(location);
}
});
};
var revertPop = function revertPop(revertPop){
var toLocation = history.location;
// TODO: We could probably make this more reliable by
// keeping a list of paths we've seen in sessionStorage.
// Instead, we just default to 0 for paths we don't know.
var toIndex = allPaths.lastIndexOf(createPath(toLocation));
if (toIndex === -1) toIndex = 0;
var fromIndex = allPaths.lastIndexOf(createPath(fromLocation));
if (fromIndex === -1) fromIndex = 0;
var delta = toIndex - fromIndex; //计算地址差
if (delta) {
forceNextPop = true;
go(delta);
}
};
var allPaths = [createPath(initialLocation)];
//.....
var history = {
length: globalHistory.length,
action: 'POP',
location: initialLocation,
createHref: createHref,
push: push,
replace: replace,
go: go,
goBack: goBack,
goForward: goForward,
block: block,
listen: listen
};
return history;
}

handleHashChange函数用于绑定在监听onhashChange事件时处理的函数,包含一系列handlePop,revertPop的操作。
代码中定义了allPaths变量来存储已经访问的地址path.这样在处理跳转不成功的时候计算allPaths中的地址差,返回原来的地址。