一、文章思路
这篇文章主要是讲解history库并在最后会带着大家一起实现一个小型history库。
二、为啥要讲history?
因为history库提供着最核心的动作API,我们经常使用的this.props.history.push等相应API都是这个库提供的。
三、history库整体分析
这个库分为2部分,一个docs、一个package;docs目录主要是对package目录的描述,package目录就是history库的核心代码。
四、docs分析
4.1、getting-started.md
这个文件主要是讲了这个包的产物分类,应用,属性。其中会遇到写API,如果想具体了解API,它就会指引你跳到api-reference.md文件。其中我觉得最重要的话就是:
根据环境,它们将创建的路由分为3种,一种是“browser history”,一种是“hash history”,一种是“memory history”。
4.2、api-reference.md
五、API分析(BrowserHistory为例)
5.1、history对象
const history = {
action (){},
location (){},
createHref (){},
push (){},
replace (){},
go (){},
back (){},
forward (){},
listen (){},
block (){}
}
这个就是history对象(源码里是ts)
5.2、action
function action (value){
const actionType = ['PUSH', 'POP', 'REPLACE'];
return actionType.filter(item => item == value)[0];
}
action主要是操作标识,何种方式操作历史堆栈
5.3、location
function getIndexAndLocation (){
let { pathname, search, hash } = window.location;
let state = window.history.state || {};
return [state.idx, {
pathname,
search,
hash,
state: state.usr || null,
}]
}
let [index, location] = getIndexAndLocation();
const history = {
get location (){
return location;
}
}
location,字如其名,描述当前路由的位置,里面保存着当前路由的相关信息,比如url, search(查询字符串)、state(自己维护的state)等等。
5.4、createHref
function createPath ({ pathname = '/', search = '', hash = '' }){
if (search && search !== '?')
pathname += search.charAt(0) == '?' search : '?' + search;
return pathname;
}
function createHref (to){
return typeof to == 'string' ? to : createPath(to);
}
const history = {
createHref
}
createHref,创建链接,说白了就是根据传参来得到url。
5.5、listen
function createEvents (){
let handlers = [];
return {
get length (){
return handlers.length;
}
push (fn){
handlers.push(fn);
return function (){
handlers = handlers.filter(handler => handler !== fn)
};
}
call (arg){
handlers.forEach( fn => fn && fn(arg) );
}
}
}
let listeners = createEvents();
const history = {
listen (fn){
return listeners.push(fn);
}
};
上面的代码就是创建一个对象obj,当调用 history.listen(fn)的时候,实际上就是执行obj.push,push这个动作会把传入的函数参数放入到自由变量handlers里面,当人为的改变当前历史堆栈后,就会触发listeners.call, 从而实现路由监听。这里需要解释一下什么是人为的改变当前历史堆栈? 就是手动this.props.history.xx() 或者 手动改变浏览器上方的前进或者后退按钮。
5.6、push
// 存放着listen里的函数参数
let listeners = createEvents();
// 创建新的history的location信息
function getNextLocation (to, state){
return {
pathname: window.location.pathname,
search: '',
hash: '',
to,
state,
key: Math.random()
}
}
// 获取路由状态和url
function getHistoryStateAndUrl (nextLocation, index){
return [
{
usr: nextLocation.state,
key: nextLocation.key,
idx: index
},
nextLocation.pathname + nextLocation.search
]
}
function push (to, state){
let nextAction = 'PUSH';
let nextLocation = getNextLocation(to, state);
let index = 0;
let [historyState, url] = getHistoryStateAndUrl(nextLocation, index + 1);
window.history.pushState(historyState, '', url);
listeners.call({ 'PUSH', { pathname, search, hash, state, key } })
}
const history = {
push
}
当我们调用 history.push('/usr', { name: '小明' }), 实际上就是在调用 window.history.pushState(state, ‘’, url), 中间的其他步骤只是在转化state,最后呢,就是触发listen函数。
六、mini-history包的实现
- 我们需要创建一个history对象
- 这个对象我们只需实现listen、push、back方法,其他方法与push方法都差不多
6.1、mini-listen的实现
简要思路:就是找一个全局变量来存放listen函数,并在push、back函数里去主动触发它
// 声明监听函数集合(全局)
let listenerArr = [];
const createBrowserHistory = () => {
function listen (fn){
listenerArr.push(fn);
}
return {
listen
}
}
6.2、mini-push的实现
简要思路:声明一个全局变量来记录当前路由,push的时候将路由的url等相关信息全都赋值到全局变量里,随后执行监听函数
// 当前路由位置信息
let currentRouteLocation = {};
// 声明监听函数集合
let listenerArr = [];
const createBrowserHistory = () => {
function push (to, state){
currentRouteLocation.pathname = to;
currentRouteLocation.state = state;
listenerArr.forEach(fn => fn && fn());
}
return {
push
}
}
现在我们来测试一下:
const history = createBrowerHistory();
history.listen(() => console.log('当前路由的信息:', currentRouteLocation) );
history.push('/usr', { name: '小明' })
输出信息如下:

好了,走到这说明我们的监听函数和push函数都已经成功了。
6.3、mini-back的实现
简要思路:声明一个全局变量historyStack, 当我们手动调用push方法时,我们也要向 historyStack里push路由信息,因为路由信息里有唯一顺序号,所以当我们调用back时,根据顺序号就能找到上一个路由。
// 用于存放历史堆栈
let historyStack = [];
// 同样记录这是第几个路由
let index = 0;
// 当前路由信息
let currentRouteLocation = {};
const createBrowerHistory = () => {
// push 方法需要做出小改动
function push (to, state){
currentRouteLocation.pathname = to;
currentRouteLocation.state = state;
// back方法会根据index来正确的回到上一层路由
currentRouteLocation.index = index;
// 需要在历史堆栈里添加一下
history.push({ ...currentRouteLocation });
listenerArr.forEach(fn => fn && fn());
index++;
}
function back (){
index -= 2;
const current = historyStack.filter(item => item.index == index);
currentRouteLocation = { ...current[0] };
listenerArr.map( fn => fn && fn() );
}
return {
back,
push
}
}
七、总结
这篇文章我们梳理了history的工作流程,其中如果有没讲到的地方或者是错误的地方欢迎大家指出,那么下一篇我将会讲解React-Route系列的最后一篇dom篇,敬请期待哈哈哈哈。
本文详细探讨了history库的重要性,整体结构,以及关键API如BrowserHistory的分析。通过实现mini-history包,解释了listen、push和back方法的工作原理,为理解React-Route奠定了基础。
672

被折叠的 条评论
为什么被折叠?



