关于 data-client-schema
data-client-schema 是指导开发者为 API JSON 定义组合式数据模式的技能,适用于使用 @data-client 的应用。它涵盖实体、联合类型、集合和查询等结构,以及规范化缓存的建模方式。
主要功能
- 01定义实体与联合模式
- 02组合对象、列表和映射
- 03建模可变集合
- 04创建记忆化查询
- 05处理嵌套与部分实体
适用场景
- 01描述 API 返回的复杂 JSON
- 02规范化关联数据并管理缓存
- 03为多态数据定义联合模式
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ reactive/ data-client --skill data-client-schema安装后在 Claude Code 中输入/data-client-schema确认已加载。
详细介绍
1. 定义架构
定义 架构,以表示端点返回的 JSON。组合这些架构来表示预期的数据。
对象
- Entity - 表示一个唯一的对象(反规范化)
- EntityMixin - 将任何已有类转换为 Entity
- new Union(Entity) - 多态对象(A | B)
{[key:string]: Schema}- 不可变对象- new Invalidate(Entity|Union) - 删除 Entity
- new Lazy(() => Schema) - 打破循环导入/延迟深度递归反规范化
列表
- new Collection([Schema]) - 可变/可扩展列表
[Schema]- 不可变列表- new All(Entity|Union) - 列出某类的所有 Entity
映射
new Collection(Values(Schema))- 可变/可扩展映射- new Values(Schema) - 不可变映射
依赖 Lens 的实体字段
- new Scalar({ lens, key, entity? }) - 随运行时 lens(投资组合、货币、区域设置)变化的字段,不会修改实体
派生/选择器模式
-
new Query(Queryable) - 经过记忆化的程序化选择器
const queryRemainingTodos = new Query( TodoResource.getList.schema, entries => entries.filter(todo => !todo.completed).length, );const groupTodoByUser = new Query( TodoResource.getList.schema, todos => Object.groupBy(todos, todo => todo.userId), );使用数据模型(例如
src/resources/)定义Query转换,而不要在自定义 hook 中封装 useSuspense/useQuery;这种做法会隐藏数据依赖,并使数据逻辑与视图代码耦合。
2. Entity 最佳实践
- 每个
Entity子类都要为所有非可选序列化字段定义默认值。 - 仅当主键 ≠
id时才覆盖pk()。 pk()的返回类型是number | string | undefined- 覆盖
Entity.process(value, parent, key, args),根据 args/url 插入字段 static schema(可选),用于嵌套架构或反序列化函数- 设计 API 时,优先嵌套实体
3. Entity 生命周期方法
- 规范化(JSON 响应 → 缓存):处理 POJO;输出为 JSON 可序列化的普通数据,存储在规范化缓存中。顺序:
process()→pk()→ validate() → 遍历嵌套架构(递归处理schema字段)→ 如果已存在:mergeWithStore(),它会调用shouldUpdate(),并可能调用shouldReorder()+merge();元数据通过mergeMetaWithStore()处理。 - 反规范化(缓存 → 组件):通过
fromJS()创建 Entity 类实例,还原原型链,使 getter、方法和schema处理正常工作。顺序:createIfValid()→ validate() →fromJS()→ 取消遍历嵌套架构(递归处理schema字段)。
4. 联合类型(多态架构)
要定义多态资源(例如事件),请使用 Union 和判别字段。
import { Union } from '@data-client/rest'; // also available from @data-client/endpoint
export abstract class Event extends Entity {
type: EventType = 'Issue'; // discriminator field is shared
/* ... */
}
export class PullRequestEvent extends Event { /* ... */ }
export class IssuesEvent extends Event { /* ... */ }
export const EventResource = resource({
path: '/users/:login/events/public/:id',
schema: new Union(
{
PullRequestEvent,
IssuesEvent,
// ...other event types...
},
'type', // discriminator field
),
});
5. 集合(可变列表与映射)
Collections 会包装 Array 或 Values 架构,以支持变更(添加/移除/移动)。
pk 路由
当嵌套在 Entity 中且可用时,pk() 会使用 nestKey(parent, key);否则使用 argsKey(...args),然后序列化结果。未提供选项时,默认使用 argsKey: params => ({ ...params }),将所有端点参数作为集合键。
argsKey— 根据端点参数派生 pk(默认)nestKey— 根据父实体派生 pk,用于嵌套的共享状态集合
在同一个 Collection 上同时定义两者,即可在顶层和嵌套场景复用同一个定义。当 argsKey(args) 和 nestKey(parent) 生成相同的对象结构时,顶层获取与嵌套读取会解析为同一个(引用相等的)数组/映射——对任一方执行 push/unshift/assign/move/remove 都会同时更新两者:
const userTodos = new Collection([Todo], {
argsKey: ({ userId }: { userId?: string }) => ({ userId }),
nestKey: (parent: User) => ({ userId: parent.id }),
});
nonFilterArgumentKeys
默认情况下,createCollectionFilter 使用 nonFilterArgumentKeys(默认值:以 'order' 开头的键)在匹配集合时排除非筛选参数。这会影响哪些现有集合会接收由 push/unshift/assign/move 添加的新项。
可将其覆盖为函数、RegExp 或 string[]:
new Collection([Todo], { nonFilterArgumentKeys: /orderBy|sortDir/ })
Extenders
这些方法均可通过 ctrl.set() 使用(仅限本地),也可通过 RestEndpoint extenders 使用(网络)。
| 方法 | 类型 | 描述 |
|---|---|---|
push | Array | Entity |
unshift | Array | Entity |
assign | Values | 将条目合并到映射中 |
remove | Both | 按值从匹配的集合中移除项 |
move | Both | 从与现有状态匹配的集合中移除,再添加到与新状态匹配的集合中 |
addWith(merge, filter?) | Both | 自定义创建模式(内部由 push/unshift/assign 使用) |
moveWith(merge) | Both | 自定义移动模式(控制插入顺序,例如使用 unshift 合并以添加到开头) |
6. 补充端点(丰富现有实体)
当端点为缓存中已有的实体返回部分数据或形状不同的数据时 (例如元数据端点、统计端点、延迟加载扩展端点), 请将同一个 Entity 用作模式,不要创建包装实体。
请参阅 partial-entities,了解相关模式和示例。
7. 最佳实践与说明
- 始终为每个资源/实体/集合设置
schema,以便进行规范化 - 通过定义适当的模式,对深层嵌套或关联数据进行规范化
- 使用
Entity.schema进行客户端连接 - 使用 rest/endpoint/graphql 中的
Denormalize<>类型,而不是 InstanceType<>。这样可以处理所有模式(例如 Unions),而不仅仅是 Entity。
8. 应避免的常见错误
- 规范化缓存存储的是可序列化为纯 JSON 的对象(POJO),而不是类实例。
- 别忘了使用
fromJS()或为类字段指定默认属性——单独的 TS 字段类型不会生成运行时默认值,因此模式推断会失效 - 手动合并或“丰富”数据;应改用
Entity.schema进行客户端连接
参考
如需查看详细的 API 文档,请参阅 references 目录:
- Entity - 规范化数据类
- EntityMixin - 将任意类转换为 Entity
- Collection - 可变/可增长列表
- Union - 多态模式
- Query - 程序化选择器
- Invalidate - 删除实体
- Lazy - 延迟/循环模式
- Scalar - 依赖于 Lens 的实体字段
- Values - 映射模式
- All - 列出某一类型的所有实体
- Array - 不可变列表模式
- Object - 对象模式
- schema - 模式概述
- relational-data - 关联数据指南
- computed-properties - 计算属性指南
- partial-entities - 部分实体指南
- side-effects - 副作用指南
- sorting-client-side - 客户端排序指南