---
description: Hooks/Composables 规范 - 组合式函数设计、输入输出策略。
globs: ["**/hooks/**/*.ts", "**/composables/**/*.ts"]
alwaysApply: false
---

# Hooks/Composables 规范

该规则用于 Hook 开发：

**当逻辑需要复用时：**

- 逻辑被 2+ 组件复用 → 提取 Hook
- 单组件逻辑 >100 行 → 提取 Hook
- 涉及生命周期 + 响应式状态 → 提取 Hook

**当设计 Hook 参数时：**

- ≤3 个独立参数 → 散开参数
- > 3 个或有关联 → 配置对象

**当设计 Hook 返回值时：**

- 返回对象，支持解构重命名
- 状态用名词，动作用动词

---

## 📁 文件组织

**方案 A：按功能平铺**（小型项目）

```
{hooks目录}/
├── useUser.ts
├── useRequest.ts
└── index.ts
```

**方案 B：按领域分组**（大型项目）

```
{hooks目录}/
├── user/
│   ├── useUser.ts
│   └── index.ts
├── common/
│   └── useRequest.ts
└── index.ts
```

> 📋 具体存放路径详见 @foundation/project.mdc

---

## 🎨 设计策略

### 1. 何时提取 Hook

| 场景                     | 是否提取  | 说明                 |
| ------------------------ | --------- | -------------------- |
| 逻辑被 2+ 组件复用       | ✅ 提取   | 复用是提取的首要理由 |
| 单组件逻辑过长（>100行） | ✅ 提取   | 拆分职责，提升可读性 |
| 涉及生命周期+响应式状态  | ✅ 提取   | 封装副作用管理       |
| 简单的单次计算           | ❌ 不提取 | 用 `computed` 即可   |
| 仅读取 props/emit        | ❌ 不提取 | 组件内直接处理       |

### 2. 输入设计

**散开参数**（≤3 个，无关联）：

```typescript
export function useToggle(initialValue = false) {}
export function useDebounce<T>(value: Ref<T>, delay = 300) {}
```

**配置对象**（>3 个，或有关联）：

```typescript
interface UseRequestOptions<T> {
  api: () => Promise<T>;
  immediate?: boolean;
  onSuccess?: (data: T) => void;
}

export function useRequest<T>(options: UseRequestOptions<T>) {}
```

### 3. 输出设计

**原则**：返回对象，解构友好

```typescript
export function useToggle(initial = false) {
  const state = ref(initial);
  const toggle = () => (state.value = !state.value);
  return { state, toggle };
}

// 使用时可重命名
const { state: visible, toggle: toggleVisible } = useToggle();
```

**命名约定**：

| 返回类型 | 命名规则        | 示例                         |
| -------- | --------------- | ---------------------------- |
| 状态     | 名词            | `data`, `loading`, `state`   |
| 动作     | 动词            | `execute`, `toggle`, `reset` |
| 计算值   | is/has/can 前缀 | `isLogin`, `hasData`         |

### 4. 组合策略

**垂直组合**：Hook 内部调用其他 Hook

```typescript
export function useUserList() {
  const { data, loading, execute } = useRequest(getUserListApi);
  const { state: showModal, toggle } = useToggle();

  onMounted(() => execute());

  return { userList: data, loading, showModal, toggleModal: toggle };
}
```

**水平组合**：多个 Hook 在组件中并行使用

```typescript
const { userInfo } = useUser();
const { state: editing, toggle: toggleEdit } = useToggle();
const { execute: save, loading: saving } = useRequest(saveApi);
```

---

## ✅ 检查清单

### 设计

- ✅ Hook 必须返回响应式数据或函数
- ✅ Hook 必须在 `setup` 或其他 Hook 顶层调用
- ✅ Hook 内部使用的副作用必须自动清理

### 禁止

- ❌ 禁止在条件语句/循环中调用 Hook
- ❌ 禁止在普通函数中调用 Hook
- ❌ 禁止 Hook 内部有未清理的定时器/事件监听

> 📋 命名规范详见 @foundation/naming.mdc

---

## 📋 标准模板

### 基础状态 Hook

```typescript
import { ref, computed } from "vue";

export function useCounter(initial = 0) {
  const count = ref(initial);
  const double = computed(() => count.value * 2);

  const increment = () => count.value++;
  const decrement = () => count.value--;
  const reset = () => (count.value = initial);

  return { count, double, increment, decrement, reset };
}
```

### 异步请求 Hook

```typescript
import { ref, onMounted, onUnmounted } from "vue";

export function useRequest<T>(api: () => Promise<T>) {
  const data = ref<T>();
  const loading = ref(false);
  const error = ref<Error>();

  async function execute() {
    loading.value = true;
    error.value = undefined;
    try {
      data.value = await api();
    } catch (e) {
      error.value = e as Error;
    } finally {
      loading.value = false;
    }
  }

  return { data, loading, error, execute };
}
```
