History
History分类
- browserHistory
- hashHistory
CreateBrowserHistory
在react-router-dom中的<BrowserHistory>组件中有这么几行代码:
let historyRef = React.useRef<BrowserHistory>();
if (historyRef.current == null) {
historyRef.current = createBrowserHistory({ window });
}
那么这个createBrowserHistory函数究竟干了些什么事情呢?
简单来说,它返回了一个类型为BrowserHistory的对象,上面封装了方法和携带了属性,就如下图所示(省略了back和forward方法)
那我还是比较好奇,这里面到底干了些什么事情,想一探究竟。
前情提要:这里不去展开讲block相关的事情,因为我自己也没弄懂,主要focus在push replace这两个方法上。
History.push
不管三七二十一,先把代码贴出来。
function push(to: To, state?: any) {
let nextAction = Action.Push;
let nextLocation = getNextLocation(to, state);
function retry() {
push(to, state);
}
if (allowTx(nextAction, nextLocation, retry)) {
let [historyState, url] = getHistoryStateAndUrl(nextLocation, index + 1); // index表示当前的页面的idx
try {
globalHistory.pushState(historyState, "", url);
} catch (error) {
window.location.assign(url);
}
applyTx(nextAction);
}
}
第一行没啥好讲的,相当于给 nextAction 赋值为 PUSH
第二行这里我们来看下 getNextLocation这个函数
其实也没什么骚操作,仅仅就是返回了一个Location对象,这个对象是经过Object.freeze处理过的。
function getNextLocation(to: To, state: any = null): Location {
return readOnly<Location>({
pathname: location.pathname,
hash: "",
search: "",
...(typeof to === "string" ? parsePath(to) : to),
state,
key: createKey(),
});
}
继续往下看,定义了一个retry函数,这里我们忽略即可,涉及到了block
然后做了一个判断allowTx,这里我们也是默认为true,这个主要是用来判断页面跳转是否被阻塞了
接着看getHistoryStateAndUrl这个函数
到这里,感觉都很好理解,仅仅就是返回了最新的一个HistoryState和一个URL
function getHistoryStateAndUrl(nextLocation: Location, index: number): [HistoryState, string] {
return [
{
usr: nextLocation.state,
key: nextLocation.key,
idx: index,
},
createHref(nextLocation), // 返回一个url
];
}
function createHref(to: To) {
return typeof to === "string" ? to : createPath(to);
}
然后通过globalHistory上的api,来实现路由压栈的操作
最后的关键是applyTx(nextAction)这个操作,废话不多说先来看代码。
function applyTx(nextAction: Action) {
action = nextAction; // 更新history对象内部的action属性
[index, location] = getIndexAndLocation(); // 更新history对象内部的index和location属性
listeners.call({ action, location }); // 调用listensers.call方法
}
这个listeners是啥东东捏?先简单看下代码:
interface Update {
action: Action;
location: Location;
}
interface Listener {
(update: Update): void;
}
let listeners = createEvents<Listener>();
function createEvents<F extends Function>(): Events<F> {
let handlers: F[] = [];
return {
get length() {
return handlers.length;
},
push(fn: F) {
handlers.push(fn);
return function () {
handlers = handlers.filter((handler) => handler !== fn);
};
},
call(arg) {
handlers.forEach((fn) => fn && fn(arg));
},
};
}
简单来理解:
- createEvents通过闭包的方式声明了一个队列(数组)
- createEvents返回了一个对象,上面有get、push、call三种方法,而这个对象实际上就是
listeners。 - 通过调用
listeners.call({ action, location }),实际上就是把闭包内的那个队列中的函数给执行了一遍。
那么问题来了,我们是怎么把函数注册进这个listensers中的队列的呢?
可以看history上的listen方法:
function createBrowserHistory(options: BrowserHistoryOptions = {}): BrowserHistory {
// some codes
let history: BrowserHistory = {
... other methods,
listen(listener) {
return listeners.push(listener);
}
};
return history;
}
History.replace
其实replace和push大同小异,就不加赘述。
function replace(to: To, state?: any) {
let nextAction = Action.Replace;
let nextLocation = getNextLocation(to, state);
function retry() {
replace(to, state);
}
if (allowTx(nextAction, nextLocation, retry)) {
let [historyState, url] = getHistoryStateAndUrl(nextLocation, index);
// TODO: Support forced reloading
globalHistory.replaceState(historyState, "", url);
applyTx(nextAction);
}
}
\