用 Schema 思考
设想一篇典型的博客文章。单篇文章的 API 响应可能如下所示:
{
"id": "123",
"author": {
"id": "1",
"name": "Paul"
},
"title": "My awesome blog post",
"comments": [
{
"id": "324",
"createdAt": "2013-05-29T00:00:00-04:00",
"commenter": {
"id": "2",
"name": "Nicole"
}
},
{
"id": "544",
"createdAt": "2013-05-30T00:00:00-04:00",
"commenter": {
"id": "1",
"name": "Paul"
}
}
]
}
声明式定义
我们的 article 中嵌套了两种 Entity 类型:users 和 comments。借助各种 schema,我们可以把这三种 Entity 类型全部规范化:
- TypeScript
- JavaScript
import { schema, Entity } from '@data-client/endpoint';
import { Temporal } from 'temporal-polyfill';
class User extends Entity {
id = '';
name = '';
}
class Comment extends Entity {
id = '';
createdAt = Temporal.Instant.fromEpochMilliseconds(0);
commenter = User.fromJS();
static schema = {
commenter: User,
createdAt: Temporal.Instant.from,
};
}
class Article extends Entity {
id = '';
title = '';
author = User.fromJS();
comments: Comment[] = [];
static schema = {
author: User,
comments: [Comment],
};
}
import { schema, Entity } from '@data-client/endpoint';
import { Temporal } from 'temporal-polyfill';
class User extends Entity { }
class Comment extends Entity {
static schema = {
commenter: User,
createdAt: Temporal.Instant.from,
};
}
class Article extends Entity {
static schema = {
author: User,
comments: [Comment],
};
}
规范化
import { normalize } from '@data-client/normalizr';
const args = [{ id: '123' }];
const normalizedData = normalize(Article, originalData, args);
现在,normalizedData 会为所有 Entity 创建一个可序列化的单一事实来源:
{
result: "123",
entities: {
articles: {
"123": {
id: "123",
author: "1",
title: "My awesome blog post",
comments: [ "324", "544" ]
}
},
users: {
"1": { "id": "1", "name": "Paul" },
"2": { "id": "2", "name": "Nicole" }
},
comments: {
"324": {
id: "324",
createdAt: "2013-05-29T00:00:00-04:00",
commenter: "2"
},
"544": {
id: "544",
createdAt: "2013-05-30T00:00:00-04:00",
commenter: "1"
}
}
},
// contents excluded for brevity
indexes,
entitiesMeta,
}
反规范化
import { denormalize } from '@data-client/normalizr';
const denormalizedData = denormalize(
Article,
normalizedData.result,
normalizedData.entities,
args,
);
现在,denormalizedData 会实例化这些类,确保同一成员(例如 Paul)的所有实例在引用上相等:
Article {
id: '123',
title: 'My awesome blog post',
author: User { id: '1', name: 'Paul' },
comments: [
Comment {
id: '324',
createdAt: Instant [Temporal.Instant] {},
commenter: [User { id: '2', name: 'Nicole' }]
},
Comment {
id: '544',
createdAt: Instant [Temporal.Instant] {},
commenter: [User { id: '1', name: 'Paul' }]
}
]
}
MemoCache
MemoCache 是一个单例,可用于在多次调用之间保持引用相等,同时还可能把性能提升 2000%。它的方法都经过了记忆化。
memo.denormalize
import { MemoCache } from '@data-client/normalizr';
// you can construct a new memo anytime you want to reset the cache
const memo = new MemoCache();
const { data, paths } = memo.denormalize(
Article,
normalizedData.result,
normalizedData.entities,
args,
);
const { data: data2 } = memo.denormalize(
Article,
normalizedData.result,
normalizedData.entities,
args,
);
// referential equality maintained between calls
assert(data === data2);
memo.denormalize() 与上面的 denormalize() 完全一样,只是返回值中还包含 paths。paths
是一个数组,包含结果中所有 Entity 的路径。
memo.query
memo.query() 允许仅根据参数(而不是规范化后的输入)对 Queryable 进行反规范化。
const data = memo.query(
Article,
args,
normalizedData,
);
Queryable
Queryable Schema 无需 endpoint 即可访问 store。它们通过
queryKey 方法实现这一点,该方法会生成通常存储在 endpoint 缓存中的结果。
这使它们可以用于以下额外场景:
- useQuery() - 在 React 中渲染
- schema.Query() - 作为输入,生成经过记忆化的计算结果。
- ctrl.get/snap.get
- Managers
- 在 React 中通过 useController()
- RestEndpoint.getOptimisticResponse
- 使用 renderDataHook() 进行 hook 的单元测试
- memo.query()
- 在 endpoint 解析之前就进行渲染,从而提升 useSuspense、useDLE 的性能
Querables 包括 Entity、All、Collection、Query、
Union 和 Scalar。Lazy 字段可以通过其 .query 访问器得到一个 Queryable。
interface Queryable {
queryKey(
args: readonly any[],
queryKey: (...args: any) => any,
getEntity: GetEntity,
getIndex: GetIndex,
// `{}` means non-void
): {};
}
Schema 概览
| 数据类型 | 可变 | Schema | 描述 | 可查询 |
|---|---|---|---|---|
| 对象 | ✅ | Entity | 单个唯一对象 | ✅ |
| ✅ | Union(Entity) | 多态对象(A | B) | ✅ | |
| 🛑 | Object | 静态已知的键 | 🛑 | |
| Invalidate(Entity) | 删除 Entity | 🛑 | ||
| 列表 | ✅ | Collection(Array) | 可增长的列表 | ✅ |
| 🛑 | Array | 不可变列表 | 🛑 | |
| All | 某一类型的全部 Entity 列表 | ✅ | ||
| 映射 | ✅ | Collection(Values) | 可增长的映射 | ✅ |
| 🛑 | Values | 不可变映射 | 🛑 | |
| Scalar | ✅ | Scalar | 依赖视角(lens)的 Entity 字段 | ✅ |
| 任意 | Query(Queryable) | 记忆化的自定义转换 | ✅ | |
| Lazy(Schema) | 延迟反规范化 | ✅ |