# useRequest

useRequest 是一个用于处理数据请求的 Vue 3 Composition API Hook

## 核心配置

| 配置项        | 类型                       | 说明                                                                                                                                                   |
| ------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| useUniLoading | boolean                    | 是否需要调用 uni.showLoading 显示加载提示，默认不开启                                                                                                  |
| ready         | boolean\|\| Ref\<boolean\> | 请求触发前置条件：1. 当值从 false 变为 true 时，会自动发起请求 2. 当值为 false 时，请求永远不会被触发3. 支持普通布尔值或响应式布尔值（Ref`<boolean>`） |
| manual        | boolean                    | 是否手动触发请求： 为 true 时，请求不会自动执行，需通过 run/runAsync 手动触发 为 false 时，满足前置条件后自动发起请求                                  |
| defaultParams | P                          | 请求默认参数，发起请求时会作为基础参数传入 注意：分页参数请放入 paginationConfig 中配置，不建议在此传入                                                |
| refreshDeps   | any[]                      | 依赖刷新数组，当数组中的任意元素发生变化时，会自动重新发起请求，用于实现「依赖变更刷新数据」的场景                                                     |

## 加载优化配置

| 配置项       | 类型   | 说明                                                                                                                                |
| ------------ | ------ | ----------------------------------------------------------------------------------------------------------------------------------- |
| loadingDelay | number | 延迟结束加载提示的时间，单位为毫秒 作用：防止请求响应过快造成加载提示闪烁，提升用户体验，例如配置 300 表示加载提示至少显示 300 毫秒 |

## 轮询配置

| 配置项                 | 类型   | 说明                                                                                        |
| ---------------------- | ------ | ------------------------------------------------------------------------------------------- |
| pollingInterval        | number | 轮询间隔时间，单位为毫秒 使用规则：通过 run/runAsync 方法启动轮询，通过 cancel 方法取消轮询 |
| pollingErrorRetryCount | number | 轮询错误重试次数 作用：当轮询过程中请求失败时，会自动重试指定次数，重试失败后停止轮询       |

## 错误重试配置

| 配置项          | 类型   | 说明                                                                                        |
| --------------- | ------ | ------------------------------------------------------------------------------------------- |
| errorRetryCount | number | 普通请求（非轮询）的错误重试次数 作用：当单次请求失败时，会自动重试指定次数，提升请求成功率 |
| errorRetryDelay | number | 错误重试间隔时间，单位为毫秒 作用：配置两次重试之间的等待时间，避免频繁重试给服务端造成压力 |

## 防抖配置

| 配置项           | 类型    | 说明                                 |
| ---------------- | ------- | ------------------------------------ |
| debounceWait     | number  | 防抖等待时间，单位为毫秒             |
| debounceLeading  | boolean | 是否在防抖开始前执行调用（前置执行） |
| debounceTrailing | boolean | 是否在防抖结束后执行调用（后置执行） |

## 节流配置

| 配置项           | 类型    | 说明                                 |
| ---------------- | ------- | ------------------------------------ |
| throttleWait     | number  | 节流等待时间，单位为毫秒             |
| throttleLeading  | boolean | 是否在节流开始前执行调用（前置执行） |
| throttleTrailing | boolean | 是否在节流结束后执行调用（后置执行） |

## 分页配置

| 配置项           | 类型    | 说明                                                                |
| ---------------- | ------- | ------------------------------------------------------------------- |
| usePagination    | boolean | 是否启用分页功能，启用后会自动整合分页参数和分页返回结果            |
| paginationConfig | Object  | 分页详细配置对象，仅当 usePagination: true 时生效，具体子配置见下表 |

## paginationConfig 子配置

| 子配置项        | 类型   | 默认值     | 说明                                                                         |
| --------------- | ------ | ---------- | ---------------------------------------------------------------------------- |
| currentKey      | string | 'current'  | 后端接收的「当前页码」参数名，例如后端要求传 pageNum，则配置该值为 'pageNum' |
| pageSizeKey     | string | 'pageSize' | 后端接收的「每页数量」参数名，例如后端要求传 size，则配置该值为 'size'       |
| totalKey        | string | 'total'    | 后端返回结果中「总数据条数」的字段名，用于解析总页数                         |
| defaultCurrent  | number | 1          | 默认当前页码，初始化时的起始页码                                             |
| defaultPageSize | number | 10         | 默认每页显示数据条数，初始化时的每页数量                                     |
| listKey         | string | 'list'     | 后端返回结果中「分页数据列表」的字段名，用于解析分页数据集合                 |

## 生命周期回调配置

| 配置项    | 类型                                        | 说明                                                     |
| --------- | ------------------------------------------- | -------------------------------------------------------- |
| onBefore  | () => Promise\<void\> \| void               | 请求发起前的回调函数，支持同步和异步操作（返回 Promise） |
| onSuccess | (result: R\| undefined, params?: P) => void | 请求成功后的回调函数                                     |
| onError   | (error: any) => void                        | 请求失败后的回调函数                                     |
| onFinally | () => void                                  | 请求完成后的回调函数（无论成功或失败都会执行）           |

## 返回结果

| 返回属性     | 类型说明                                  | 功能与使用说明                                                                                               |
| ------------ | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| data         | R                                         | 存储请求成功后的返回结果数据，初始化时为 undefined，请求成功后更新为后端返回的有效数据                       |
| setData      | (value: R\| undefined) => void            | 手动设置 data 数据的方法，可用于手动更新返回结果缓存，例如手动重置数据、修改部分返回结果                     |
| loading      | boolean                                   | 存储当前请求的加载状态                                                                                       |
| setLoading   | (value: boolean) => void                  | 手动设置 loading 加载状态的方法，可用于特殊场景下手动控制加载提示的显示与隐藏                                |
| run          | (params?: P) => void                      | 手动触发请求（同步风格），可传入可选参数 params 覆盖默认参数（defaultParams）                                |
| runAsync     | (params?: P) => Promise\<R \| undefined\> | 手动触发请求（异步风格，返回 Promise），可传入可选参数 params 覆盖默认参数                                   |
| refresh      | () => void                                | 刷新当前请求（同步风格），不支持传入新参数，沿用上次请求的参数重新发起请求，适用于「重新加载当前数据」的场景 |
| refreshAsync | () => Promise\<R \| undefined\>           | 刷新当前请求（异步风格，返回 Promise），不支持传入新参数，沿用上次请求的参数重新发起请求                     |
| cancel       | () => void                                | 取消当前正在进行的请求                                                                                       |
| pagination   | Object                                    | undefined 仅当 option.usePagination: true 时返回有效分页对象，否则为 undefined                               |
| list         | array                                     | 合并分页后的数据，建议current起始为1时使用该数据，防止数据不连续                                             |

## 使用示例

```ts
<template>
  <view class="content">
    <view>data:{{ data }}</view>
    <view>loading:{{ loading }}</view>
    <button @click="onTestRun">测试 run</button>
    <button @click="onTestRunAsync">测试 runAsync</button>
    <button @click="onTestRefresh">测试 refresh</button>
    <button @click="onTestRefreshAsync">测试 refreshAsync</button>
    <button @click="onTestRefreshDeps">测试 refreshDeps</button>
    <button @click="onTestCancel">测试 cancel</button>
    <button @click="onTestReady">测试 ready {{ ready }}</button>
    <button @click="onTestDebounce">测试 debounce</button>
    <button @click="onTestThrottle">测试 throttle</button>
    <button @click="onTestPagination">测试分页</button>
  </view>
</template>
<script lang="ts" setup>
import useRequest from '../../dev/v3/hooks/useRequest/index';
import { watch, ref } from 'vue';

const num = ref(0);

const ready = ref(false);

const {
  data,
  loading,
  pagination,
  run,
  runAsync,
  refresh,
  refreshAsync,
  cancel,
} = useRequest(
  async (params: { a: number; b?: number }) => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        // resolve({
        //   a: 1,
        // });
        // reject(new Error('error'));

        resolve({
          current: 1,
          pageSize: 10,
          total: 100,
          list: [
            { a: 1 },
            { a: 2 },
            { a: 3 },
            { a: 4 },
            { a: 5 },
            { a: 6 },
            { a: 7 },
            { a: 8 },
            { a: 9 },
            { a: 10 },
          ],
        });
      }, 1000);
    });
  },
  {
    // manual: true,
    useUniLoading: true,
    defaultParams: [
      {
        a: 1,
      },
    ],
    refreshDeps: [num],
    // pollingInterval: 3 * 1000,
    // pollingErrorRetryCount: 3,
    // errorRetryCount: 3,
    // errorRetryDelay: 3 * 1000,
    // ready,
    // debounceWait: 3 * 1000,
    // debounceLeading: true,
    throttleWait: 3 * 1000,
    throttleLeading: true,
    usePagination: true,
    onBefore: () => {
      console.log('before');
    },
    onSuccess: (result, params) => {
      console.log('result', result);
      console.log('params', params);
    },
    onError: (err) => {
      console.log('err', err);
    },
    onFinally: () => {
      console.log('finally');
    },
  },
);

watch(
  data,
  (val) => {
    console.log('val', val);
  },
  {
    immediate: true,
  },
);

function onTestRun() {
  run({ a: 1 });
}
async function onTestRunAsync() {
  const res = await runAsync({ a: 1 });
  console.log('onTestRunAsync: ', res);
}
function onTestRefresh() {
  refresh();
}

async function onTestRefreshAsync() {
  const res = await refreshAsync();
  console.log('refreshAsync: ', res);
}

function onTestRefreshDeps() {
  num.value++;
}

function onTestCancel() {
  cancel();
}

function onTestReady() {
  ready.value = !ready.value;
}

function onTestDebounce() {
  console.log('onTestDebounce');
  run({ a: 123 });
}
function onTestThrottle() {
  console.log('onTestThrottle');
  run({ a: 456 });
}

function onTestPagination() {
  // run({ a: 1 }, { current: 1, pageSize: 10 });
  pagination?.next();
  console.log('pagination: ', pagination);
}
</script>

<style scoped>
.content {
  padding: 16px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
}

button {
  width: 100%;
}
</style>


```
