更多内容,请访问:两种路由模式及React-Router中的History库源码分析
history源码
history 在 v5 之前使用单独的包, v6 之后再 router 包中单独实现。
Action
路由切换的动作类型,包含三种类型:
- POP
- REPLACE
- PUSH
Action 枚举:
export enum Action {
Pop = "POP",
Push = "PUSH",
Replace = "REPLACE",
}
关于三种动作类型意思,可以进入Actions了解。
createLocation(current, to , state, key)
创建一个含有唯一值key的location对象。
当前方法是一个公共方法,在createBrowserHistory/createHashHistory/createMemoryHistory中都使用其创建location对象。
如果你需要更深层次了解,请进入createLocation。
getUrlBasedHistory(getLocation, createHref, validateLocation, options)
createBrowserHistory/createHashHistory函数都基于getUrlBasedHistory,执行getUrlBasedHistory后,会返回一个history对象。
History库
history库文件暴露出createMemoryHistory、createBrowserHistory、createHashHistory三个方法,每种方法作用不一样。
-
createMemoryHistory:用于非 dom 环境,react-native 和测试环境
-
createBrowserHistory/createHashHistory:用于浏览器环境,createBrowserHistory对应于history路由模式,而createHashHistory应用于hash模式路由,两者方法的底层都是利用了HTML5 history API方法实现(即监听popstate事件及replaceState、pushState无刷新更改location URL)
createBrowserHistory/createHashHistory函数都基于getUrlBasedHistory ,提供不同的:
- getLocation
- createHref
- validateLocation
- options
属性实现不同的 history 对象
执行步骤
![history-library](https://i-blog.csdnimg.cn/blog_migrate/9f90974e84231dc0ec33686861343ed5.jpeg)
![history-library](https://i-blog.csdnimg.cn/blog_migrate/4b42d90525185416e83c4a787026ef09.jpeg)
history对象属性和方法
-
action
当前location对象变化的动作类型,关于三种动作类型意思,可以进入Actions了解。
-
location
返回当前的location对象。
底层:getLocation => createLocation()
具体内容可访问createLocation。
-
listen(fn: Listener)
在createBrowserHistory函数(此刻以此举例)中,会有一个listener变量来接收传入的监听回调函数fn。
注意:一个history中,有且仅有一个活跃的listen监听函数,否则会抛出一个异常。
如果你想要继续传入一个监听回调事件,你可以先执行history.listen(fn)的返回值(作用:清除监听事件),再传入fn。
监听location变化的函数,传入一个回调函数fn,并将代表location变化的一个update对象传入回调函数中。
内部逻辑:
(1) 创建一个popstate监听事件,回调函数handlePop
(2) 将listener = fn;
(3)返回一个函数(作用:执行这个函数,可以取消当前的listener);
源码:
listen(fn: Listener) { if (listener) { throw new Error("A history only accepts one active listener"); } window.addEventListener(PopStateEventType, handlePop); listener = fn; return () => { window.removeEventListener(PopStateEventType, handlePop); listener = null; }; },
Listener interface
export interface Listener { (update: Update): void; }
Update interface
export interface Update { action: Action; // 动作类型 location: Location; // 新的location对象 delta: number | null; // 目的location对象(也可以理解为新的location对象)与之前的location,在history栈中之间的增量 }
-
createHref(to)
创建地址
createBrowserHistory
如果to是一个string,返回to,否则createPath(to),如果想了解createPath,请访问createPath。
内部调用: createBrowserHref(window, to)
function createBrowserHref(window: Window, to: To) { return typeof to === "string" ? to : createPath(to); }
createHashHistory
如果to是一个string,返回to,否则createPath(to),如果想了解createPath,请访问createPath。
内部调用: createBrowserHref(window, to)
function createHashHref(window: Window, to: To) { let base = window.document.querySelector("base"); let href = ""; if (base && base.getAttribute("href")) { let url = window.location.href; let hashIndex = url.indexOf("#"); href = hashIndex === -1 ? url : url.slice(0, hashIndex); } return href + "#" + (typeof to === "string" ? to : createPath(to)); }
-
go(n)
指定跳转地址,调用和HTML5 history api的go方法一样, 如果想了解原生的history,请访问history。
-
push
添加一个新的历史记录
function push(to: To, state?: any) { // 1. 更改动作类型action action = Action.Push; // 2. 创建一个新的location对象 let location = createLocation(history.location, to, state); if (validateLocation) validateLocation(location, to); // 3. 当前索引idx index = getIndex() + 1; // 4. state状态对象{ idx: index,usr: state, key: 唯一的key值 } let historyState = getHistoryState(location, index); let url = history.createHref(location); // try...catch because iOS limits us to 100 pushState calls :/ try { globalHistory.pushState(historyState, "", url); } catch (error) { if (error instanceof DOMException && error.name === "DataCloneError") { throw error; } window.location.assign(url); } if (v5Compat && listener) { // 执行location变化的监听回调事件listener--调用history.listen中传入的事件 listener({ action, location: history.location, delta: 1 }); } }
-
replace
替换当前的历史记录
function replace(to: To, state?: any) { // 1. 更改动作类型action action = Action.Replace; // 2. 创建location对象 let location = createLocation(history.location, to, state); if (validateLocation) validateLocation(location, to); // 3. 返回state状态对象中的idx,否则返回null index = getIndex(); // 生成一个新对象,包含usr、key、idx: let historyState = getHistoryState(location, index); // 创建新的URL path let url = history.createHref(location); // history API中的replaceState替换当前的历史记录 globalHistory.replaceState(historyState, "", url); if (v5Compat && listener) { listener({ action, location: history.location, delta: 0 }); } }