@luban-hooks/use-request

React Hooks for fetching data, based on axios

Usage no npm install needed!

<script type="module">
  import lubanHooksUseRequest from 'https://cdn.skypack.dev/@luban-hooks/use-request';
</script>

README

@hooks/use-request

一个基于 axios 的管理异步请求的 Hooks.

安装

npm i @luban-hooks/use-request

# 未安装 axios
npm i axios@0.19.2

使用

默认请求

useRequest 接收的第一个参数是一个返回了 Promise 的异步函数(以下简称 service 函数),并且该 Promise resolved 的值必须是符合 Axios Response Schema 的。在组件首次加载是便会触发该函数执行,同时返回 data, error, loading 等状态。

import React, { FunctionComponent } from "react";
import axios from "axios";
import { useRequest } from "@luban-hooks/use-request";

interface ResponseData<T> {
  code: number;
  msg?: string;
  data: T;
}

interface UserItem {
  id: number;
  name: string;
}

function getUserList(params: getUserListQuery) {
  const url = params.name ? `/users?name=${params.name}` : "/users";
  return request.get<ResponseData<UserItem[]>>(url);
}

const User: FunctionComponent = () => {
  const { data: userList, loading, error } = useRequest(getUserList, {
    initialData: [],
    defaultParams: {},
    formatter: (res) => res.data.data,
  });

  if (error) {
    return <div>something wrong</div>;
  }

  if (loading) {
    return <div>loading...</div>;
  }

  return (
    <ul>
      {userList.map((user) => {
        return <li key={user.id}>{user.name}</li>;
      })}
    </ul>
  );
};

手动触发

useRequest 传递第二个参数并且设置 manualtrue,此时并不会在组件首次加载时触发 service 函数,而是需要手动的执行 run 函数。

import React, { FunctionComponent } from "react";
import axios from "axios";
import { useRequest } from "@luban-hooks/use-request";

interface ResponseData<T> {
  code: number;
  msg?: string;
  data: T;
}

function addUser(params: { name: string }) {
  return request.post<ResponseData<boolean>>(`/user/${params.name}`);
}

const User: FunctionComponent = () => {
   const { run: putAddUser } = useRequest(addUser, {
    manual: true,
    onSuccess: (data) => {
      if (data.code === 1) {
        // TODO do something
      }
    },
  });

  return (
    <ul>
      {/* TODO display user list */}
      <button type="button" onClick={() => putAddUser({ name: "brendan" })}>Add</button>
    </ul>
  );
};

API

const {
  response,
  data,
  error,
  loading,
  run,
  refresh,
  reset,
} = useRequest(service, {
  manual,
  defaultLoading,
  initialData,
  defaultParams,
  onSuccess,
  onError,
  verifyResponse,
  formatter,
  update,
  reFetcherDeps,
});

返回

response

@description: service 函数返回的数据,其数据结构符合 Axios Response Schema 的。

@type: AxiosResponse<any>

data

@description: service 函数返回的 response.data;如果设置了 options.formatter, 该值会被 formatter 返回的值覆盖。

@type: AxiosResponse["data"] | any

error

@description: service 执行过程中抛出的异常。

@type:AxiosError<R["data"]> | null

loading

@description: service 是否正在执行。

@type:boolean

run

@description: 手动触发 service 函数执行,参数将会传递给 service 函数。

@type:(params: any) => Promise<void>

reset

@description: 重置状态,包括 loading, error, data

@type:() => void

refresh

@description: 重新执行 service 函数,并使用上一次使用的参数。

@type:() => Promise<void>

setData

@description: 接受一个函数,允许读取或直接修改 data

@type:(setter: (data: D) => D | void) => void;

参数

service

一个返回了 Promise 的异步函数,并且该 Promise resolved 的值必须是符合 Axios Response Schema 的。否则 data 可能会不符合预期。

options

以下参数都是可选的。

manual

@description: 是否需要手动触发 service 函数

@type:boolean

@default:false

update

@description: 为 true 时,将会以 initialData 更新 data

@type:boolean

@default:false

reFetcherDeps

@description: 设置调用 run refresh 方法时的依赖,当依赖变化时,run refresh 方法将不再返回一个记忆回调函数。这是一个详细的例子

@type:unknown[]

@default:[]

defaultLoading

@description: 默认 loading 状态

@type:boolean

@default:false

initialData

@description: 初始的 data

@type:any

@default:{}

defaultParams

@description: 默认传递给 service 的参数

@type:any

@default:undefined

onSuccess

@description: service 执行成功的回调

@type:(data: AxiosResponse["data"], response: AxiosResponse<any>) => void | (data: AxiosResponse["data"], response: AxiosResponse<any>, params: any) => void;

@default:() => undefined

onError

@description: service 执行失败的回调

@type:(error: AxiosError<AxiosResponse["data"]>) => void | (error: AxiosError<AxiosResponse["data"]>, params: any) => void;

@default:() => undefined

verifyResponse

@description: 校验 service 函数返回的数据是否符合预期

@type:((response: AxiosResponse<any>) => boolean)

@default:() => true

formatter

@description: 对 service 函数返回的数据进行转换

@type:(response: AxiosResponse<any>) => any

@default:(res) => res.data

同时存在 formatteronSuccess 参数:

formatteronSuccess 参数同时存在时,使用 onSuccess 的第一个参数时,需要显式的标明其类型;formatter 参数不存在时,则 onSuccess 的第一个参数不需要显示的标明类型:

 const { data: userList, run: fetchUserList } = useRequest(getUserList, {
   initialData: [],
   defaultParams: {},
   formatter: (res) => res.data.data,
   // 需要使用 `formatter` 返回的值类型来标明 `onSuccess` 第一个参数的类型,确保 `userList` 的类型被正确的推导
   // 当 `formatter` 参数不传,`onSuccess` 的第一个参数则不需要显示的标明类型!!! 
   onSuccess: (data: UserItem[], res, params) => {
     console.log(data, res, params);
   },
 });

全局配置

可以通过 UseRequestProvider 在根组件设置一些全局配置。

import { UseRequestProvider } from "@luban-hooks/use-request";

interface ResponseData<T> {
  code: number;
  msg?: string;
  data: T;
}

<UseRequestProvider<AxiosResponse<ResponseData<any>>
  value={{
    verifyResponse: (res) => res.status === 200 && res.data.code === 1,
    onSuccess: () => console.log("global success"),
    onError: (error) => console.log(error, "global error"),
    manual: false,
    defaultLoading: true,
    initialData: [],
  }}
>
  <App />
</UseRequestProvider>

UseRequestProvider 并不支持全局的 formatterdefaultParams 参数

获取最新的状态

比如有以下这个例子:

import React, { FunctionComponent } from "react";
import axios from "axios";
import { useRequest } from "@luban-hooks/use-request";

interface ResponseData<T> {
  code: number;
  msg?: string;
  data: T;
}

function addUser(params: { name: string }) {
  return request.post<ResponseData<boolean>>(`/user/${params.name}`);
}

const User: FunctionComponent = () => {
  const [count, setCount] = useState(0);

  const { run: putAddUser } = useRequest(addUser, {
    manual: true,
    onSuccess: (data) => {
      if (data.code === 1) {
        console.info(count);
        // TODO do something
      }
    },
  });

  const addUserAndUpdateCount = () => {
    setCount(1);
    putAddUser({ name: "brendan" })
  };

  return (
    <ul>
      {/* TODO display user list */}
      <button type="button" onClick={addUserAndUpdateCount}>Add</button>
    </ul>
  );
};

上面这个例子在添加用户的同时更新了计数器 count,然后在 onSuccess 回调中打印了 count,你会发现这里 count 的值仍然是 0,但是 count 已经确实更新为了 2。 这是因为 use-request 在内部实现时,使用了 useCallbackrun 方法进行了处理。可以设置 reFetcherDeps 来告诉 useCallback 不再返回旧的函数:

// ...
 const { run: putAddUser } = useRequest(addUser, {
    manual: true,
    reFetcherDeps: [count],
    onSuccess: (data) => {
      if (data.code === 1) {
        console.info(count);
        // TODO do something
      }
    },
  });
// ...

这样,当在 onSuccess 函数再次打印 count 时,就是最新的值了。