ahooks 使用指南:按场景选 Hook 与治理异步请求
本文目录
ahooks 是一组面向 React 的可复用 Hooks。它可以减少常见状态、事件和请求逻辑的重复实现,但不是 React 必需依赖,也不会自动修复任意闭包、权限或数据一致性问题。使用前先确认当前项目的 React 主版本、SSR 模式、依赖策略和团队约定。
什么时候值得引入
适合考虑 ahooks 的场景:
- 团队反复实现相似的请求加载、轮询、分页、事件订阅或浏览器交互逻辑。
- 现有项目已经使用并维护 ahooks,希望沿用成熟的约定。
- 需要从官方 Hook 目录中寻找有测试和文档的通用实现,再按本项目约束集成。
不必为了“React 全家桶”给一个简单页面添加依赖。若需求只用几行 useState、useEffect 就能清楚表达,优先保持直接;若框架已经提供路由加载或服务器状态缓存,也要比较是否重复建设。
安装与版本核对
在新项目中先看 React 官方对框架和构建工具的建议;确定 ahooks 能解决具体问题后,再用项目正在使用的包管理器安装,并将版本锁定在 lockfile 中:
npm install ahooks
不要在同一仓库混用 npm、pnpm、Yarn 生成不同 lockfile。升级前查看 ahooks 当前发布说明和对应版本文档;官网文档、仓库主分支、项目 lockfile 可能处在不同时间点。官方 README 目前已说明 useUrlState 使用独立包 @ahooks.js/use-url-state,不能只凭旧文章中的导入路径安装。
useRequest:把请求状态显式呈现
useRequest 可封装异步 service 的 loading、data、error 等常见状态。手动触发适合由用户动作发起的请求;自动请求则要结合输入参数变化、组件生命周期和路由数据方案评估:
import { useRequest } from "ahooks";
type Profile = {
id: string;
displayName: string;
};
function parseProfile(value: unknown): Profile {
if (typeof value !== "object" || value === null) {
throw new Error("用户资料格式无效");
}
const record = value as Record<string, unknown>;
if (typeof record.id !== "string" || typeof record.displayName !== "string") {
throw new Error("用户资料字段无效");
}
return { id: record.id, displayName: record.displayName };
}
async function loadProfile(userId: string): Promise<Profile> {
const response = await fetch(`/api/profiles/${encodeURIComponent(userId)}`);
if (!response.ok) throw new Error(`请求失败:${response.status}`);
return parseProfile(await response.json());
}
function ProfilePanel({ userId }: { userId: string }) {
const { data, error, loading, run } = useRequest(loadProfile, { manual: true });
return (
<section>
<button type="button" disabled={loading} onClick={() => run(userId)}>
{loading ? "加载中…" : "加载资料"}
</button>
{error && <p role="alert">{error.message}</p>}
{data && <p>{data.displayName}</p>}
</section>
);
}
TypeScript 类型只在编译期帮助开发者;网络 JSON 仍是不可信输入,示例中的解析步骤负责运行时校验。服务端也必须单独执行身份认证、资源授权和字段验证。
请求状态与边界
- 明确区分首次加载、后台刷新、空数据、失败、取消和无权限状态,别把“没有数据”一律显示成空白。
- 参数变化时验证请求是否自动重跑、旧响应如何处理、卸载时如何清理。当前文档提供
cancel等能力,但取消客户端等待不等于服务端没有收到或处理请求。 - 对
cacheKey、缓存时间和共享请求,先明确键是否包含租户、账号和查询参数;缓存不是权限边界,不能让不同用户共享敏感数据。 run、runAsync、回调顺序和异常传播会随版本/API 约定影响调用方;读当前版本文档,并为调用方显式写出成功、失败和取消路径。- 轮询、重试和防抖都可能放大服务压力。配置最大次数、退避策略、页面隐藏行为和清理逻辑,并与服务端限流策略配合。
闭包与函数引用:辅助 Hook 不是免责符
ahooks 提供 useLatest、useMemoizedFn 等辅助 Hook。它们可以在特定模式下读取最新值或维持函数引用,但仍要理解闭包捕获、Effect 依赖和引用属性语义。不要为消掉 lint 警告而机械改写所有回调;先确认所需的是最新值、稳定身份,还是正确的同步时机。
- 若值会影响 UI,使用 state,而不是通过 ref 隐藏更新。
- 若 Effect 读取响应式值,完整声明依赖并设计 cleanup。
- 若子组件因函数引用变化而重复 render,先用 Profiler 测量,再选择
useCallback、useMemoizedFn或调整组件边界。 - 对返回函数的自定义属性、异常、this 绑定、延迟回调和卸载时调用等边界,以项目采用版本的文档和测试为准。
SSR、浏览器 API 与可测试性
“支持 SSR”不意味着任意 Hook 都能在服务端读取 window、DOM 或存储,也不意味着请求数据会自动安全地注入 HTML。检查具体 Hook 是否延迟访问浏览器对象,服务端是否会执行请求,以及 hydration 前后状态是否一致。涉及 target、监听器和计时器时,验证目标不存在、目标动态变化、Strict Mode 重入和卸载清理。
测试不只断言 Hook 返回值:
- 请求成功、失败、手动触发、重复触发和参数切换。
- 旧响应不会覆盖新参数对应的界面;取消后不会产生过时提示。
- 组件卸载后监听器、定时器和订阅已清理。
- 缓存隔离符合账号/租户边界,缓存失效后能刷新。
- 键盘操作、加载提示、错误播报和禁用状态可被真实用户感知。
选择一个 Hook 前的清单
- 它解决的是重复逻辑,还是只是把几行代码换成另一套 API?
- React、SSR、浏览器和 TypeScript 版本是否在该 Hook 的支持范围内?
- 谁拥有状态,何时请求,如何取消,失败时显示什么?
- 是否有缓存、重试或轮询导致的权限/服务压力边界?
- 团队能否维护该抽象,依赖是否与框架能力重叠?
- 是否能通过测试和性能测量证明引入后更可靠或更易读?
官方资料
Live Notes
留言提交后会先进入审核,审核通过后对其他读者公开。