HTML5 history API简介
| 方法/属性 | 说明 | 语法示例 |
|---|---|---|
| pushState() | 将新的历史记录条目添加到浏览器的历史记录栈,更新 URL,但不刷新页面。 | history.pushState(state, title, url); |
| replaceState() | 替换当前历史记录条目,更新 URL,但不刷新页面。 | history.replaceState(state, title, url); |
| back() | 相当于点击浏览器的“后退”按钮,回到历史记录栈中的前一个页面。 | history.back(); |
| forward() | 相当于点击浏览器的“前进”按钮,跳到历史记录栈中的下一个页面。 | history.forward(); |
| go() | 跳到历史记录栈中的指定位置(正数为前进,负数为后退)。 | history.go(n); |
| length | 返回历史记录栈中的记录数。 | history.length |
| state | 返回当前历史记录条目的 state 对象(仅在调用 pushState() 或 replaceState() 后有效)。 | history.state |
history 库的核心功能
v5 用于 React Router v 6。
v4 在 React Router 版本 4 和 5 中使用。
在API的区别上 v5将v4的goBack替换为back,forward同理。
// history v4 的 history 对象
const history = {
length: globalHistory.length,
action: 'POP',
location: initialLocation,
createHref,
push,
replace,
go,
goBack,
goForward,
block,
listen
};
// history v5 的 history 对象
let history: HashHistory = {
get action() {
return action;
},
get location() {
return location;
},
createHref,
push,
replace,
go,
back() {
go(-1);
},
forward() {
go(1);
},
listen(listener) {
return listeners.push(listener);
},
block(blocker) {
let unblock = blockers.push(blocker);
if (blockers.length === 1) {
window.addEventListener(BeforeUnloadEventType, promptBeforeUnload);
}
return function () {
unblock();
// Remove the beforeunload listener so the document may
// still be salvageable in the pagehide event.
// See https://html.spec.whatwg.org/#unloading-documents
if (!blockers.length) {
window.removeEventListener(BeforeUnloadEventType, promptBeforeUnload);
}
};
},
};
| 功能 | 描述 | 语法示例 |
|---|---|---|
| createBrowserHistory() | 创建一个可以使用浏览器的 history.pushState() 和 history.replaceState() API 的历史记录对象,支持路径管理和 URL 操作。 | import { createBrowserHistory } from 'history';const history = createBrowserHistory(); |
| createHashHistory() | 创建一个使用哈希路由的历史记录对象,适用于不支持 HTML5 history API 的环境。 | import { createHashHistory } from 'history';const history = createHashHistory(); |
| createMemoryHistory() | 创建一个内存中的历史记录对象,适用于服务端渲染或非浏览器环境,历史记录不会影响浏览器的地址栏。 | import { createMemoryHistory } from 'history';const history = createMemoryHistory(); |
| push() | 在历史记录栈中添加一个新的条目,同时更新地址栏 URL。通常用于导航到新页面。 | history.push('/new-page'); |
| replace() | 替换当前历史记录条目,更新地址栏 URL,但不添加新的历史记录条目。 | history.replace('/new-page'); |
| go() | 跳转到历史记录栈中的某个位置,正数前进,负数后退。 | history.go(-1); // 后退1步 |
| back() | 与 history.go(-1) 相同,回到历史记录栈中的上一个条目。 | history.back(); |
| forward() | 与 history.go(1) 相同,前进到历史记录栈中的下一个条目。 | history.forward(); |
| listen() | 监听历史记录的变化,当 URL 改变时触发回调函数。 | const unlisten = history.listen(location => { console.log(location); }); |
| location | 当前的历史记录条目的位置对象,包含当前的 URL 路径、查询参数、hash 等信息。 | console.log(history.location.pathname); |
| createHref() | 将一个给定的 location对象转换为一个可以用于浏览器地址栏的 路径字符串。 | const href = history.createHref(location); |
history v5 源码分析
createHashHistory 和 createBrowserHistory的区别
createHashHistory 和 createBrowserHistory的区别就是前者直接通过 pushState 和 replaceState 来更新历史记录,同时修改浏览器的 URL。后者通过修改 window.location.hash 来更新 URL,但也使用 window.history 对象的 pushState 和 replaceState 以类似的方式进行管理历史状态。在事件监听上,createHashHistory除了popstate之外,它还需要额外监听 hashchange 事件。因为在一些旧浏览器(如 IE 11 和旧版 Edge)中,popstate 事件在哈希变化时不会触发,所以需要使用 hashchange 事件来处理哈希的变化。
history.location
在对象实例就返回出由getIndexAndLocation的useState
function getIndexAndLocation(): [number, Location] {
let { pathname, search, hash } = window.location;
let state = globalHistory.state || {};
return [
state.idx,
readOnly<Location>({
pathname,
search,
hash,
state: state.usr || null,
key: state.key || 'default'
})
];
}
history.listen
在 createBrowserHistory 中,listen 方法就是使用了观察者模式。它允许开发者注册一系列回调函数(观察者),这些回调会在浏览器历史状态(即 URL 和历史记录)发生变化时被触发,从而实现页面的动态更新。
function applyTx(nextAction: Action) {
action = nextAction;
[index, location] = getIndexAndLocation();
listeners.call({ action, location });
}
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));
},
};
}
history.push() & history.replace()
调用push和replace时在未被block阻塞时会执行applyTx,和原生的pushState的区别就是封装了url和状态的匹配,通过一个统一的函数 getHistoryStateAndUrl 来同时生成历史记录的状态和 URL,从而避免了开发者手动去维护这两者的同步,也对ios限制100次pushState做了边界处理。
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);
// try...catch 对ios限制100次pushState做了边界处理
try {
globalHistory.pushState(historyState, "", url);
} catch (error) {
// They are going to lose state here, but there is no real
// way to warn them about it since the page will refresh...
window.location.assign(url);
}
applyTx(nextAction);
}
}
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);
globalHistory.replaceState(historyState, "", url);
applyTx(nextAction);
}
}
listen,block去监听go、back方法则是通过监听popstate事件去触发handlePop,在handlePop里若没有注册阻塞函数,则执行applyTx,执行监听回调函数的同时更新index和location。
为什么go、back通过监听popstate事件去执行监听回调,因为原生的back()、forward(),会被popstate监听到,而调用 history.pushState() 或者 history.replaceState() 不会触发 popstate 事件。
popstate 事件只会在浏览器某些行为下触发,比如点击后退按钮(或者在 JavaScript 中调用 history.back() 方法)。即,在同一文档的两个历史记录条目之间导航会触发该事件。
history相比于原生API的优势
- push、replace相比于pushState、replaceState调用更加简洁,无需再传递unused参数。且push、replace会执行listen监听时传入的回调函数。
- 原生history对象需要手动解析和管理 location(路径、查询字符串、哈希等),history提供了一个封装好的 location 对象,它包括了路径、查询字符串、哈希值等信息,并且这些信息可以在历史记录中进行自动跟踪。
- createBrowserHistory 封装了 history.pushState 和 history.replaceState,并且它提供了 自动处理页面状态 和 历史状态 的功能。它会通过一个统一的函数 getHistoryStateAndUrl 来同时生成历史记录的状态和 URL,从而避免了开发者手动去维护这两者的同步。原生的API 只关心 URL 和 state,它们本身并不关心页面的其他状态(如页面渲染的内容)。如果在 URL 改变时更新页面的内容,你需要自己手动处理页面状态和历史记录之间的一致性。
- createBrowserHistory 提供了 listen 和 block 方法,允许开发者更精确地控制历史记录的行为,特别是在需要拦截用户的导航行为时。
- block:可以阻止某些导航行为,直到特定条件满足。这对于需要确认用户是否丢失未保存数据的场景非常有用。例如,在用户离开当前页面时弹出确认框。
- listen:允许开发者监听历史记录的变化,例如页面路径的改变,可以精确地捕捉到任何历史记录的变化。
原生的 window.history API 没有类似的机制,开发者需要自己在 popstate 事件中实现这些逻辑。
React Router v4、v5 和 v6 的差异
| 特性 | React Router v4 | React Router v5 | React Router v6 |
|---|---|---|---|
| 路由组件 | <Route component={...}> | <Route render={...}> | <Route element={...}> |
| 动态路由 | 使用 render 和 children | 使用 render 和 children | 使用 element 属性 |
| 路由切换容器 | <Switch> | <Switch> | <Routes> |
| 嵌套路由支持 | 支持 | 支持 | 支持 |
| 重定向组件 | <Redirect /> | <Redirect /> | <Navigate /> |
| history.push | 使用 history.push() | 使用 history.push() | 使用 useNavigate() |
| exact 匹配模式 | 需要设置 exact | 需要设置 exact | 默认精确匹配,无需设置 exact |
| 权限控制 | 手动实现 | 手动实现 | 更加简洁,直接在路由配置中处理 |
| 组件 | 支持 | 支持 | 不再支持 |
history和hash模式区别
| 特性 | history 模式 | hash 模式 |
|---|---|---|
| URL 结构 | 使用标准的 URL(没有 #),例如 /page1 | 使用 # 字符,例如 /page1#about |
| 浏览器行为 | 不会导致页面刷新,支持 SEO(如果配置了服务器端路由) | URL 中的 # 部分变化不会引起页面刷新,历史记录不干扰页面加载 |
| 浏览器支持 | 需要浏览器支持 HTML5 History API(现代浏览器基本都支持) | 兼容所有浏览器,支持老版浏览器 |
| 服务器要求 | 需要服务器配置支持单页应用路由(服务器需要处理 404) | 不需要特殊服务器配置,直接可以运行 |
| SEO 支持 | 有较好的 SEO 支持,服务器可以返回正确的 HTML 页面 | 对 SEO 支持差,因为 URL 中的 # 部分不被搜索引擎索引 |
| URL 变化时页面刷新 | 不刷新页面,完全由 JavaScript 控制路由变化 | 不刷新页面,只有 hash 部分发生变化 |
| 历史记录管理 | 更强大的历史记录控制,支持 pushState 和 replaceState | 简单的历史记录管理,hashchange 事件 |
| 用户体验 | URL 规范,更像传统的网页应用,支持前进和后退 | URL 通过 # 跳转,可能影响用户体验 |