# Ahooks 使用指南（基础、进阶与常见用法）

Ahooks 是一套高质量、可复用的 React Hooks 集合，适合在业务工程中快速构建稳定的交互与数据逻辑。本文档覆盖快速上手、基础用法、`useRequest` 核心与进阶、常见场景模板、最佳实践与坑位排查，结合本项目技术栈（React 18 + Vite + MUI）。

目录
- 为什么选 Ahooks
- 安装与类型支持
- 快速上手：页面标题（`useTitle`）
- 基础能力：状态/事件/定时/存储/性能
- useRequest 基础与进阶（自动/手动、轮询、缓存、SWR、重试等）
- 常见场景模板（搜索防抖、任务轮询、分页、依赖请求、自动保存）
- 最佳实践与常见坑位
- 速查表与参考资料

## 为什么选用 Ahooks

- 大量生产验证的 Hooks，覆盖状态管理、请求、DOM、浏览器、性能优化等场景。
- API 统一、可组合，适合封装成业务级自定义 Hook。
- 降低维护成本：内置防抖/节流、轮询、缓存、并发控制、卸载安全、SWR 等复杂能力。

## 安装与类型支持

```bash
pnpm add ahooks
```

TypeScript 用户无需额外配置，Ahooks 提供完整的类型提示。

## 快速上手：更新页面标题（useTitle）

最直接的需求就是“更新页面标题”。`useTitle` 在组件挂载时设置 `document.title`，卸载时可恢复。

```tsx
import { useTitle } from 'ahooks';

export default function KnowledgeListPage() {
  useTitle('TERES · 知识库列表', { restoreOnUnmount: true });
  return (<div>...</div>);
}
```

如需统一加前后缀，可封装：

```tsx
import { useTitle } from 'ahooks';

export function usePageTitle(title?: string, opts?: { prefix?: string; suffix?: string }) {
  const final = [opts?.prefix, title, opts?.suffix].filter(Boolean).join(' · ');
  useTitle(final || 'TERES', { restoreOnUnmount: true });
}
```

## 基础能力（常用 Hooks）

- useBoolean / useToggle：布尔与枚举状态切换
  ```tsx
  const [visible, { setTrue, setFalse, toggle }] = useBoolean(false);
  ```

- useDebounceFn / useThrottleFn：防抖与节流（输入、滚动、窗口变化）
  ```tsx
  const { run: onSearchChange, cancel } = useDebounceFn((kw: string) => setKeywords(kw), { wait: 300 });
  ```

- useEventListener：声明式事件监听（自动清理）
  ```tsx
  useEventListener('resize', () => console.log(window.innerWidth), { target: window });
  ```

- useLocalStorageState / useSessionStorageState：状态持久化
  ```tsx
  const [lang, setLang] = useLocalStorageState('lang', { defaultValue: 'zh' });
  ```

- useInterval / useTimeout：定时任务（自动清理）
  ```tsx
  useInterval(() => refresh(), 30000);
  ```

- useMemoizedFn：稳定函数引用，避免子组件无谓重渲染/解绑
  ```tsx
  const onNodeClick = useMemoizedFn((id: string) => openDetail(id));
  ```

- useLatest：防陈旧闭包，异步/周期函数读取最新数据
  ```tsx
  const latestState = useLatest(state);
  useInterval(() => { doSomething(latestState.current); }, 1000);
  ```

- useSafeState / useUnmount：卸载安全，避免内存泄漏或报错
  ```tsx
  const [data, setData] = useSafeState<any>(null);
  useUnmount(() => cancelRequest());
  ```

## useRequest（核心与进阶）

`useRequest` 是 Ahooks 的异步数据管理核心，内置自动/手动触发、轮询、防抖/节流、屏幕聚焦重新请求、错误重试、loading 延迟、SWR（stale-while-revalidate）缓存等能力。[0][1]

### 基础用法（自动请求）

```tsx
import { useRequest } from 'ahooks';
import agentService from '@/services/agent_service';

export default function AgentList() {
  const { data, loading, error, refresh } = useRequest(() => agentService.fetchAgentList());
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {String(error)}</div>;
  return <div>{JSON.stringify(data)}</div>;
}
```

### 手动触发与参数管理

```tsx
const { run, runAsync, loading, params } = useRequest((kw: string) => agentService.searchAgents({ keywords: kw }), {
  manual: true,
  onSuccess: (data, [kw]) => console.log('searched', kw, data),
});

// 触发
run('rag');
// 或 await runAsync('rag').catch(console.error);
```

### 生命周期回调

```tsx
useRequest(api, {
  onBefore: (p) => console.log('before', p),
  onSuccess: (d) => console.log('success', d),
  onError: (e) => console.error('error', e),
  onFinally: (p, d, e) => console.log('finally'),
});
```

### 刷新上一次请求（refresh）与数据变更（mutate）

```tsx
const { run, refresh, mutate, data } = useRequest(() => api.getUser(1), { manual: true });

// 修改页面数据，不等待接口返回
mutate((old) => ({ ...old, name: 'New Name' }));
// 使用上一次参数重新发起请求
refresh();
```

### 取消响应（cancel）与竞态取消

```tsx
const { run, cancel } = useRequest(api.longTask, { manual: true });
run();
// 某些场景需要忽略本次 promise 的响应
cancel();
```

### 轮询与停止条件

```tsx
const { data, cancel } = useRequest(() => agentService.getJobStatus(), {
  pollingInterval: 5000,
  pollingWhenHidden: false,
  onSuccess: (res) => { if (res.done) cancel(); },
});
```

### 防抖 / 节流 / 重试 / 延迟 / 焦点刷新

```tsx
useRequest(searchService.query, {
  manual: true,
  debounceWait: 300,
  throttleWait: 1000,
  retryCount: 2,
  loadingDelay: 200,
  refreshOnWindowFocus: true,
});
```

### 缓存（cacheKey）与过期时间（staleTime）

```tsx
const { data } = useRequest(() => agentService.fetchAgentList(), {
  cacheKey: 'agent:list',
  staleTime: 60_000, // 1分钟内复用缓存（SWR 策略）
});
```

### 条件就绪（ready）与依赖刷新（refreshDeps）

```tsx
useRequest(() => api.getById(id), {
  ready: !!id,
  refreshDeps: [id],
});
```

### 默认参数（defaultParams）

```tsx
const { data } = useRequest((id: number) => api.getById(id), {
  defaultParams: [1],
});
```

## 常见场景模板

### 搜索输入防抖

```tsx
const { run: search } = useRequest((kw: string) => api.search({ keywords: kw }), { manual: true });
const { run: onChange, cancel } = useDebounceFn((kw: string) => search(kw), { wait: 300 });
```

### 任务状态轮询，完成后停止

```tsx
const { data, cancel } = useRequest(() => api.getJobStatus(jobId), {
  pollingInterval: 5000,
  pollingWhenHidden: false,
  onSuccess: (res) => { if (res.done) cancel(); },
});
```

### 分页列表（前端分页）

```tsx
const { data, loading } = useRequest(() => api.list({ page, pageSize, keywords }), {
  refreshDeps: [page, pageSize, keywords],
});
```

### 依赖请求（根据上游结果触发下游）

```tsx
const userReq = useRequest(() => api.getUser(userId), { ready: !!userId });
const postsReq = useRequest(() => api.getPosts(userReq.data?.id), {
  ready: !!userReq.data?.id,
});
```

### 自动保存（组件生命周期安全）

```tsx
useInterval(async () => {
  if (!graphValid) return;
  await api.setAgentDSL(payload);
}, 30_000);
```

## 最佳实践与常见坑位

- 顶层调用 Hooks，避免条件语句中调用，保持顺序一致。
- 依赖项变动可能导致频繁触发；`useMemoizedFn` 可保持函数引用稳定。
- 轮询需显式停止；结合 `cancel()` 与 `pollingWhenHidden=false`。
- 缓存需设置合理 `staleTime`，避免过时数据；SWR 适合只读列表数据。
- 组件卸载时自动忽略响应，避免卸载后 setState。
- SSR 环境下注意 `window/document` 可用性，必要时使用 `useIsomorphicLayoutEffect`。

## 与本项目的结合建议

- 知识库列表搜索：将 `setTimeout` 防抖替换为 `useDebounceFn`，代码更简洁且可取消。
- 任务状态轮询：用 `useRequest` 的 `pollingInterval + cancel()` 替代手写 `setInterval`。
- 自动保存：使用 `useInterval`，确保随组件生命周期清理（我们已修复泄漏问题）。
- 事件绑定：用 `useEventListener` 管理 `window`/`document` 事件，自动清理。
- 页面标题：用 `useTitle` 或统一的 `usePageTitle` 封装。

## 速查表（精选）

- 状态：`useBoolean`、`useToggle`、`useSetState`、`useControllableValue`
- 请求：`useRequest`
- DOM/事件：`useEventListener`、`useInViewport`、`useClickAway`
- 定时：`useInterval`、`useTimeout`
- 存储：`useLocalStorageState`、`useSessionStorageState`
- 性能/安全：`useDebounceFn`、`useThrottleFn`、`useMemoizedFn`、`useLatest`、`useSafeState`

## 参考资料

- 官方文档（useRequest 快速上手）[0] https://ahooks.js.org/zh-CN/hooks/use-request/index
- 官方文档（useRequest 基础用法）[1] https://ahooks.js.org/zh-CN/hooks/use-request/basic
- 项目仓库：https://github.com/alibaba/hooks

---

实战建议：如果只需要“更新 web 标题”，直接在页面组件里使用 `useTitle`；若需统一标题规范或根据路由动态拼接，封装一个 `usePageTitle` 更可维护。`useRequest` 作为核心请求管理，优先在网络交互中使用它的自动/手动、轮询、缓存与重试能力，替代手写的定时器与状态机。