@sinoui/use-rest-list-api

[![npm version](https://img.shields.io/npm/v/@sinoui/use-rest-list-api)](https://www.npmjs.com/package/@sinoui/use-rest-list-api) [![downloads](https://img.shields.io/npm/dm/@sinoui/use-rest-list-api)](https://www.npmjs.com/package/@sinoui/use-rest-list-a

Usage no npm install needed!

<script type="module">
  import sinouiUseRestListApi from 'https://cdn.skypack.dev/@sinoui/use-rest-list-api';
</script>

README

use-rest-list-api

npm version downloads

不分页列表与 RESTful CRUD API 交互的状态管理

它可以帮助我们:

  • 管理列表的查询
  • 与 RESTful CRUD API 交互
  • 列表数据维护
  • 查询条件与浏览器的 url 同步

目录:

安装

yarn add @sinoui/use-rest-list-api

或者

npm i --save @sinoui/use-rest-list-api

快速使用

import React from 'react';
import useRestListApi from '@sinoui/use-rest-list-api';

interface User {
  userId: string;
  userName: string;
}

function ListDemo() {
  const dataSource = useRestListApi<User>('/apis/users');

  return (
    <div>
      {dataSource.isLoading && <div>正在加载人员列表数据...</div>}
      <h1>人员列表</h1>
      {dataSource.items.map((user) => (
        <div key={user.userId}>{user.userName}</div>
      ))}
    </div>
  );
}

RESTful CRUD API

假定我们要维护一组人员数据,获取人员列表的 url 是/users

获取列表数据

请求

GET /users?sex=male&sort=firstName&sort=lastName,desc

请求参数说明:

  • sex=male - 表示列表的过滤条件。@sinoui/use-rest-list-api 默认将过滤条件放在查询字符串中。
  • sort - 排序,默认格式为propertyName[,asc|desc],如果有多个,则按照sort=propertyName&sort=propertyName2,desc这样的方式编排

注意:这是@sinoui/use-rest-list-api 默认发送分页查询请求的格式,你的 RESTful API 如果不是这样的,那么你需要定制列表查询请求

响应

后端返回 json 格式数据,数据如下:

[
  {
    id: '1',
    firstName: '张',
    lastName: '三',
    sex: 'male',
  },
  {
    id: '2',
    firstName: '李',
    lastName: '四',
    sex: 'male',
  },
];

注意:如果你的 API 响应的数据格式不是这样的,那么你可以定制列表查询响应转换器,将 API 响应数据转换成上面说的数据格式即可。

获取单个数据

有时为了展现详情数据,而列表返回的数据不是很全,这时你就需要通过 API 获取单个数据。

请求

按照 RESTful 风格设计的 API,请求如下:

GET /users/1

响应

返回 JSON 格式数据。

{
  "id": "1",
  "firstName": "张",
  "lastName": "三",
  "sex": "male",
  "birthday": "1999-01-12"
}

注意:如果你的 API 响应数据格式不一致,你可以通过定制请求单个数据响应转换器,来转换成这样的数据格式。

新增数据

请求

POST /users

请求参数: (itemInfo: T,isNeedUpdate: boolean = true, idx: number = -1)

// 要新增的数据
{
  "firstName": "王",
  "lastName": "五",
  "sex": "female",
  "birthday": "2000-08-12"
},
// 新增操作完成之后是否需要刷新页面,默认true
false,
// 指定新增数据的插入位置,默认-1,在数据的末尾添加
-1

注意:如果你的 API 请求要新增的数据格式不一致,你可以通过定制新增请求的数据转换器,将上面的数据格式转换成满足你的 API 的数据格式。

响应

返回 JSON 格式的数据:

{
  "id": "3",
  "firstName": "王",
  "lastName": "五",
  "sex": "female",
  "birthday": "2000-08-12"
}

注意:如果你的 API 响应数据格式不一致,你可以通过定制新增响应的数据转换器,将上面的数据格式转换成满足你的 API 的数据格式。

更新数据

请求

PUT /users/3

请求体是 JSON 格式数据:

{
  "id": "3",
  "firstName": "王",
  "lastName": "五",
  "sex": "male",
  "birthday": "2000-08-12"
}

注意:如果你的 API 请求数据格式不一致,你可以通过定制更新请求的数据转换器,将上面的数据格式转换成满足你的 API 的数据格式。

响应

返回 JSON 格式的数据:

{
  "id": "3",
  "firstName": "王",
  "lastName": "五",
  "sex": "male",
  "birthday": "2000-08-12"
}

注意:如果你的 API 响应数据格式不一致,你可以通过定制更新响应的数据转换器,将上面的数据格式转换成满足你的 API 的数据格式。

删除数据

请求

删除单个数据:

DELETE / users / 1

删除多条数据:

DELETE /users/1,2,3

注意:如果你的 API 不支持删除多条数据,那么请设置options.useMultiDeleteApifalse

响应

返回 200、201 等 2xx 状态码表示删除成功即可。

数据结构

排序信息

排序:

interface SortInfo {
  direction: 'desc' | 'asc';
  property: string;
}

列表查询响应

useRestListApi 默认列表查询的数据结构如下:

T[]

useRestListApi 参数说明

const dataSource = useRestListApi<T, PageData>(
    url: string,
    defaultValue?: PageData<T>,
    options?: Options
);

url

指定加载列表数据的url,一般为 RESTful CRUD API 中加载列表的url,也就是基础 url。加载列表数据的 url 与基础 url 不一致,可以通过options.baseUrl设定基础 url。

defaultValue

指定默认的列表分页数据,默认为:

T[]

options

配置:

  • baseUrl - 指定 curd api 的基础url,如果不指定,则默认为url
  • defaultSearchParams - 指定默认的查询条件。
  • defaultSort - 指定默认的排序规则。
  • syncToUrl - 如果为true,则会同步查询条件与浏览器 URL。默认为false
  • keyName - 指定唯一键属性名,默认为id
  • useMultiDeleteApi - 是否启动删除多条数据的 API。默认为true,表示启用。见删除数据章节。
  • transformListResponse - 指定分页列表查询结果的转换器。
  • transformListRequest - 指定分页查询条件转换器。
  • transformFetchOneResponse - 指定获取单条数据的响应数据转换器。
  • transformSaveRequest - 指定新增数据的请求数据转换器。
  • transformSaveResponse - 指定新增数据的响应数据转换器。
  • transformUpdateRequest - 指定更新数据的请求数据转换器。
  • transformUpdateResponse - 指定更新数据的响应数据转换器。
  • transformRemoveResponse - 指定删除数据的响应数据转换器。

转换器可以用来定制你的 API 细节。会用一个章节来介绍。

转换器

如果你的 API 数据格式与@sinoui/use-rest-list-api 默认支持的不同,那么你可以使用转换器来实现定制,让@sinoui/use-rest-list-api 为你的 API 服务。

定制列表查询请求

使用transformListRequest来定制列表查询请求。例如下面的转换器:

import qs from 'qs';

export default function transformListRequest(
  searchParams: {
    [key: string]: string;
  },
  sorts: SortInfo[],
) {
  return qs.stringify(
    {
      ...searchParams,
      sort: sorts.map(
        (sortInfo) =>
          `${sortInfo.property}${sortInfo.direction === 'desc' ? '_desc' : ''}`,
      ),
    },
    {
      arrayFormat: 'comma',
    },
  );
}

应用这个转换器后,发送的分页列表查询将会是下面的格式:

GET /users?sex=male&sort=firstName,lastName_desc

推荐使用qs来处理请求参数的序列化和解析。这里用到了arrayFormat配置,设定为comma,那么遇到数组时,则会采用","的方式将多个数据连接在一起。arrayFormat 的几个参数如下所示:

qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'indices' });
// 'a[0]=b&a[1]=c'
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'brackets' });
// 'a[]=b&a[]=c'
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'repeat' });
// 'a=b&a=c'
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'comma' });
// 'a=b,c'

transformListRequest 方法结构如下:

interface SearchParams {
  [key: string]: any;
}

/**
 * 转换列表查询的请求
 *
 * @param searchParams 查询条件
 * @param sorts 排序信息
 *
 * @return {string} 返回列表查询请求的查询字符串。需要是字符串格式的。
 */
function transformListRequest(
  searchParams: SearchParams,
  sorts: SortInfo[],
): string;

定制列表查询响应转换器

使用transformListResponse来转换分页列表查询响应的数据格式。如下所示的Hacker News API转换器:

interface HackerNew {
  objectID: string;
  title: string;
  url: string;
  auth: string;
  tags: string[];
}

interface HackerNewsListResponse {
  hits: HackerNew[];
  nbHits: number;
  page: number;
  nbPages: number;
  hitsPerPage: number;
}

function transformListResponse(
  response: HackerNewsListResponse,
): PageResponse<HackerNew> {
  return response.hits,
}

transformListResponse 函数的结构如下:

function transformListPresponse<T, Response>(
  response: Response,
): PageResponse<T>;

定制请求单个数据响应转换器

使用transformFetchOneResponse定制请求单个数据响应的数据格式。例如下面的示例:

interface User {
  userId: string;
  firstName: string;
  lastName: string;
}

interface Response {
  result: User;
  status: boolean;
}

function transformFetchOneResponse(response: Response): User {
  return response.result;
}

transformFetchOneResponse 函数的结构如下:

function transformFetchOneResponse<T, Response>(respone: Response): T;

定制新增请求的数据转换器

使用transformSaveRequest定制新增数据请求转换器。例如:

interface SaveUserInfo {
  user: User;
  time: long;
}

function transformSaveRequest(user: User): SaveUserInfo {
  return {
    user,
    time: new Date().getTime(),
  };
}

transformSaveRequest 函数的数据结构如下:

function transformSaveRequest<T, NewRequestData>(
  data: T,
  headers: { [key: string]: string },
): NewRequestData;

定制新增响应的数据转换器

使用transformSaveResponse定义新增响应的数据格式转变。例如:

/**
 * 新增API响应返回数据的结构
 */
interface ResponseData {
  result: User;
  status: boolean;
  errorMessage?: string;
}

function transformSaveResponse(responseData: ResponseData): User {
  if (response.status) {
    return response.result;
  }

  throw new Error('获取数据失败');
}

transformSaveResponse 函数的数据结构如下:

function transformSaveResponse<T, Response>(response: Response): T;

定制更新请求的数据转换器

使用transformUpdateRequest定制更新请求。用法与transformSaveRequest一致。

定制更新响应的数据转换器

使用transformUpdateResponse定制更新请求。用法与transformSaveResponse一致。

定制删除响应的数据转换器

/** 删除API的响应数据结构 */
interface ResponseData {
    code:string;
    msg:string;
}

function transformRemoveResponse(response:ResponseData):void {
    if(code==='200'){
        alert('删除成功');
    } else {
        alert('删除失败');
        throw new Error(response.msg);
    }
}

dataSource 的属性和方法

const dataSource = useRestListApi<User, ListRawResponse>('/users');

我们的组件可以通过dataSource与查询结果、查询条件、RESTful API 进行沟通。

获取查询数据

 // 获取当前页列表数据
const users: User[] = dataSource.items;

// 获取id为'1'的用户数据
const user: User = dataSource.getItemById('1');

// 更新id为'1'的用户数据
const newUser = {...user, 'sex': 'female'};
dataSource.updateItem(newUser);

// 更新部分字段
dataSource.setItem('1', 'sex', 'female');
dataSource.setItem('1', { birthday: '2000-10-12' });

//替换items
dataSource.setItems([{id:'1',birthday:'2019-01-01'},{id:'2',age:32}])

// 新增
dataSource.addItem({id: '5', firstName: '赵', lastName: '六'}, false, -1);

// 删除id为'1'的用户数据
dataSource.removeItemById('3');

// 删除多条数据
dataSource.removeItemsByIds(['1', '2', '3']);

// 设置默认查询条件
dataSource.setDefaultSearchParams({userName:'张三'});

// 删除指定行的数据,从0开始
dataSource.removeItemAt(5)

// 获取原始响应数据
const rawResponse = dataSource.rawResponse;

// 获取是否正在加载列表数据的状态
const isLoading = dataSource.isLoading;

// 获取是否加载列表数据失败的状态
const isError = dataSource.isError;

注意:这里介绍的getItemByIdupdateItemsetItemaddItemremoveItemById这些方法只会与dataSource.items进行交互,不会与 RESTful CRUD API 进行交互。如果需要与 RESTful CRUD API 交互,参见与增删改查 API 交互

排序

// 按照姓氏倒序排序
dataSource.sortWith([
  {
    property: 'firstName',
    direction: 'desc',
  },
  {
    property: 'lastName',
    direction: 'asc',
  },
]);

列表查询

// 根据查询条件获取数据
dataSource.query(searchParams);

// 获取查询条件
dataSource.searchParams;
// 获取默认的查询条件
dataSource.defaultSearchParams;

// 重新获取当前页的数据
dataSource.reload();

fetch()方法是查询列表的基础方法,它的语法格式如下:

function fetch<T>(
  searchParams?: SearchParams,
  sorts?: SortInfo[],
): PageResponse<T>;

与增删改查 API 交互

// 获取id为'1'的数据
const user = await dataSource.get('1');

// 新增用户数据
const user = await dataSource.save(
  { firstName: '张', lastName: '三' },
  false,
  -1,
);

// 修改用户数据
const user = await dataSource.update({
  id: '1',
  firstName: '张',
  lastName: '三',
});

// 删除数据
await dataSource.remove('1');

// 删除多条数据
await dataSource.remove(['1', '2', '3']);

以上操作默认均会修改dataSource.items。如果不需要更新,则可以指定函数的第二个参数为false,如:

const user = await dataSource.get('1', false);