mcpField
D
data-client-rest

data-client-rest

github.com/reactive开发2.0k38 次安装今天更新
代码仓库

关于 data-client-rest

data-client-rest 是面向 React/TypeScript 应用的编码技能,指导如何用 @data-client/rest 建模并访问 REST API。内容涵盖资源、端点、类型安全、数据规范化与缓存管理。

主要功能

  1. 01定义 CRUD 资源与端点
  2. 02声明式数据模式
  3. 03REST 请求与变更操作
  4. 04乐观更新配置
  5. 05扩展资源端点

适用场景

  1. 01为 API 数据定义类型安全模型
  2. 02实现列表查询和分页
  3. 03编写 REST 数据更新逻辑

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/reactive/data-client --skill data-client-rest

安装后在 Claude Code 中输入/data-client-rest确认已加载。

详细介绍

指南:使用 @data-client/rest 进行资源建模

本项目使用 @data-client/rest,在 React/TypeScript 应用中以类型安全的方式定义、获取、规范化和更新 RESTful 资源及实体,并自动管理缓存。
生成与远程 API 交互的代码时,务必遵循这些模式。


1. 定义架构

本项目使用架构以类型安全的方式定义和规范化数据模型,并自动管理缓存。有关架构模式,请使用技能“data-client-schema”。 生成可变数据定义时,务必遵循这些模式(使用技能“data-client-schema”)。

2. 资源(resource())

  • resource() 会为常见对象的 CRUD 操作创建一组 RestEndpoints
  • 必填字段:
    • path:path‑to‑regexp 模板(带类型!)
    • schema:单个项目的声明式数据结构(通常为 Entity 或 Union)
  • 可选字段:
    • urlPrefix:主机根路径,如果不是 /
    • searchParams:MyResource.getList 中查询参数的类型(TS 泛型)
    • paginationField:添加 MyResource.getList.getPage 用于分页
    • optimistic:布尔值;为 true 时,所有变更都会乐观更新,从而提升性能
    • body:MyResource.getList.push、MyResource.getList.unshift、MyResource.update、MyResource.partialUpdate 的 body 参数类型
import { Entity, resource } from '@data-client/rest';
import { User } from './User';

export class Todo extends Entity {
  id = 0;
  user = User.fromJS();
  title = '';
  completed = false;
  createdAt = new Date();

  static key = 'Todo';
  static schema = {
    user: User,
    createdAt: (iso: string) => new Date(iso),
  }
}

export const TodoResource = resource({
  urlPrefix: 'https://jsonplaceholder.typicode.com',
  path: '/todos/:id',
  schema: Todo,
  searchParams: {} as { userId?: string | number } | undefined,
  paginationField: 'page',
  nonFilterArgumentKeys: ['orderBy'],
  optimistic: true,
});
用法
渲染
// GET https://jsonplaceholder.typicode.com/todos/5
const todo = useSuspense(TodoResource.get, { id: 5 });
// GET https://jsonplaceholder.typicode.com/todos
const todoList = useSuspense(TodoResource.getList);
// GET https://jsonplaceholder.typicode.com/todos?userId=1
const todoListByUser = useSuspense(TodoResource.getList, { userId: 1 });
变更
const ctrl = useController();
// PUT https://jsonplaceholder.typicode.com/todos/5
const updateTodo = todo => ctrl.fetch(TodoResource.update, { id }, todo);
// PATCH https://jsonplaceholder.typicode.com/todos/5
const partialUpdateTodo = todo =>
  ctrl.fetch(TodoResource.partialUpdate, { id }, todo);
// POST https://jsonplaceholder.typicode.com/todos
const addTodoToStart = todo =>
  ctrl.fetch(TodoResource.getList.unshift, todo);
// POST https://jsonplaceholder.typicode.com/todos?userId=1
const addTodoToEnd = todo => ctrl.fetch(TodoResource.getList.push, { userId: 1 }, todo);
// PATCH https://jsonplaceholder.typicode.com/todos/5
const toggleStatus = (completed: boolean) => ctrl.fetch(TodoResource.getList.move, { id }, { completed });
// DELETE https://jsonplaceholder.typicode.com/todos/5
const deleteTodo = id => ctrl.fetch(TodoResource.delete, { id });
// GET https://jsonplaceholder.typicode.com/todos?userId=1&page=2
const getNextPage = (page) => ctrl.fetch(TodoResource.getList.getPage, { userId: 1, page })

如需了解更详细的用法,请使用技能“data-client-react”或“data-client-vue”。


3. 自定义 RestEndpoint 模式

/** Stand‑alone endpoint with custom typing */
export const getTicker = new RestEndpoint({
  urlPrefix: 'https://api.exchange.coinbase.com',
  path: '/products/:product_id/ticker',
  schema: Ticker,
  pollFrequency: 2000,
});

类型提示

  • path 是第一个参数的 path‑to‑regexp 模板
  • method ≠ GET ⇒ 第二个参数为 body(除非 body: undefined)
  • 提供 searchParams / body 值 仅用于类型推断
  • 从 RestEndpoint 继承时使用 RestGenerics
getOptimisticResponse()
getOptimisticResponse(snap, { id }) {
  const article = snap.get(Article, { id });
  if (!article) throw snap.abort;
  return {
    id,
    votes: article.votes + 1,
  };
}

RestEndpoint 生命周期方法
  • 执行获取: fetchResponse() → parseResponse() → process()
    • url(urlParams): urlPrefix + path +(searchParams → searchToString())
    • getRequestInit(body): getHeaders() + method + signal

4. 扩展资源

使用 .extend() 添加或覆盖端点。

export const IssueResource = resource({
  // ...base config...
}).extend((Base) => ({
  search: Base.getList.extend({
    path: '/search/issues',
    // ...custom schema or params...
  }),
}));

5. 最佳实践与说明

  • 当要求浏览或导航到某个网址时,实际访问该网址
  • 始终为每个 resource/entity/collection 设置 schema,以便进行规范化
  • 定义单个端点或不存在变更端点时,优先使用 RestEndpoint 而不是 resource()
  • 对于 blob/文件下载及其他非 JSON 响应,请参阅 network-transform: 文件下载。

6. 应避免的常见错误

  • 不要在不使用或不需要变更端点时使用 resource()

参考资料

如需详细的 API 文档,请参阅 references 目录:

指南(用户询问这些主题时请参考):

概念(用户询问这些主题时请参考):

  • expiry-policy - 缓存失效、陈旧数据、dataExpiryLength、errorExpiryLength
  • error-policy - 错误处理、重试行为、软错误与硬错误

务必遵循这些模式,并参考官方文档了解边缘情况。优先生成符合惯例、类型安全的代码,并通过 schema 定义利用自动规范化/缓存。

评论

支持 Markdown