跳到主要内容

用 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 类型全部规范化:

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 { 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 缓存中的结果。

这使它们可以用于以下额外场景:

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)延迟反规范化✅