A lightweight React Hook for managing complex async task states with global sharing, caching, retry, and race condition control.
npm install @snowykami/use-async-task!npm
!license
!npm peer dependency version
一个轻量的 React Hook,用于管理复杂异步任务的状态。支持全局共享、缓存、轮询、重试与竞态控制。
- 🌍 跨组件共享状态 - 多个组件使用相同 taskKey 会自动共享同一份任务状态
- 💾 智能缓存 - 可配置的缓存时间,避免重复请求相同数据
- 🔄 自动重试 - 内置失败重试机制,支持可配置的重试次数
- ⚡ 竞态控制 - 快速请求时仅应用最后一次的请求结果,旧请求自动丢弃
- 🔁 轮询支持 - 内置轮询间隔,支持定时自动刷新数据
- 🎯 完整类型支持 - 100% TypeScript,提供完整的类型定义
- 📦 超轻量 - 0 外部依赖,仅依赖 React
``bash`
npm install @snowykami/use-async-task或者使用 yarn
yarn add @snowykami/use-async-task或者使用 pnpm
pnpm add @snowykami/use-async-task
`typescript
import { useAsyncTask } from '@snowykami/use-async-task';
function MyComponent() {
const { data, loading, error, execute } = useAsyncTask(
async (userId: string) => {
const res = await fetch(/api/users/${userId});user-${userId}
return res.json();
},
{
immediate: false,
maxRetries: 2,
cacheTime: 10000, // 10秒缓存
taskKey: (userId) => ,
}
);
return (
加载中...
}错误: {error}
}用户: {data.name}
}API 文档
$3
#### 参数
-
action (...args: Args) => Promise
- 要执行的异步函数
- 接收 execute() 传入的参数-
options (可选)
`typescript
{
// 挂载时或依赖变化时是否自动执行
immediate?: boolean; // 依赖列表(类似 useEffect)
dependencies?: DependencyList;
// 依赖变化时获取最新参数
getArgs?: () => Args;
// 轮询间隔(毫秒),0 表示不轮询
pollingInterval?: number;
// 最大重试次数
maxRetries?: number;
// 缓存时间(毫秒),0 表示不缓存
cacheTime?: number;
// 任务标识
// - 字符串:固定 key
// - 函数:根据参数动态生成 key
taskKey?: string | ((...args: Args) => string);
}
`-
initialTaskKey (可选)
- 初始任务 key(被 options.taskKey 覆盖)#### 返回值
`typescript
{
// 状态属性
data: T | null; // 解析后的数据
loading: boolean; // 加载状态
error: TError | null; // 错误对象(如果有)
retryCount: number; // 重试次数
lastUpdated: number | null; // 最后更新时间戳 // 方法
execute: (...args: Args) => Promise; // 手动执行任务
cancel: () => void; // 取消当前请求
reset: () => void; // 重置为初始状态
}
`使用示例
$3
使用相同
taskKey 的多个组件自动共享状态:`typescript
// 组件 A
const { data: user } = useAsyncTask(
(id: string) => fetchUser(id),
{
maxRetries: 3,
taskKey: (id) => user-${id},
}
);// 组件 B(并行运行)
const { data: user } = useAsyncTask(
(id: string) => fetchUser(id),
{
maxRetries: 3,
taskKey: (id) =>
user-${id}, // 相同的 key!
}
);// 两个组件自动同步,当数据更新时一起重新渲染
`$3
`typescript
const [page, setPage] = useState(1);const { data: users } = useAsyncTask(
(p: number) => fetchUsers(p),
{
immediate: true,
dependencies: [page],
getArgs: () => [page] as const,
taskKey: (p) =>
users-page-${p},
}
);// 改变 page 时自动触发新请求
`$3
`typescript
const [query, setQuery] = useState('');const { data: results } = useAsyncTask(
async (q: string) => searchUsers(q),
{
immediate: true,
dependencies: [query],
getArgs: () => [query] as const,
maxRetries: 1,
cacheTime: 30000, // 缓存 30 秒
taskKey: (q) =>
search-${q},
}
);// 快速输入:只处理最后一次搜索
// 重复搜索:30秒内使用缓存
`$3
`typescript
const { data: stats } = useAsyncTask(
() => fetchStats(),
{
immediate: true,
pollingInterval: 5000, // 每 5 秒刷新一次
taskKey: 'dashboard-stats',
}
);
`$3
`typescript
const { loading, error, execute } = useAsyncTask(
async (formData: FormData) => submitForm(formData),
{ immediate: false } // 不自动执行
);const handleSubmit = async (data: FormData) => {
const result = await execute(data);
if (result) console.log('提交成功!');
};
`全局状态共享原理
Hook 维护一个全局任务注册表,每个
taskKey 映射到一个共享状态:`
taskKey: 'user-123'
└─ TaskRecord
├─ state: { data, loading, error, ... }
├─ listeners: Set (所有订阅的组件)
└─ [竞态控制信息]
`当任何组件更新此状态时,所有订阅的组件自动重新渲染。
竞态控制原理
当多个请求同时进行时:
1. 每个请求获得一个序列号
2. 只有最新请求的结果会被应用
3. 旧请求的结果自动被丢弃
这确保 UI 总是显示最新请求的结果。
缓存策略
在每次
execute() 调用时检查缓存:`
if (Date.now() - lastUpdated <= cacheTime) {
// 使用缓存数据,跳过请求
return cachedData;
} else {
// 缓存过期,发起新请求
// 用新数据更新缓存
}
`不同的
taskKey 拥有独立的缓存。最佳实践
1. 使用具有描述性的 taskKey - 便于调试
`typescript
taskKey: (userId) => user-profile-${userId} // ✓ 好
taskKey: (x) => ${x} // ✗ 模糊
`2. 使用 dependencies 时总是提供 getArgs
`typescript
{
dependencies: [page],
getArgs: () => [page] as const, // ✓ 提供新值
}
`3. 根据数据新鲜度设置合适的缓存时间
`typescript
cacheTime: 0, // 不缓存(实时数据)
cacheTime: 5000, // 5 秒(不那么关键的数据)
cacheTime: 300000, // 5 分钟(参考数据)
`4. 对不稳定的端点使用重试
`typescript
maxRetries: 0, // 不重试(关键且需要快速失败)
maxRetries: 3, // 重试最多 3 次(标准)
``- ES2020+
- React 16.8+(需要 Hooks 支持)
MIT © snowykami
欢迎提交 Issue 和 PR!