GQLEntity
GraphQL 有一种标准方式来定义 pk,即使用 id 字段。
GQLEntity 自动带有一个 id 字段,用作 pk。
GQLEntity 继承自 Entity
用法
import { GQLEntity } from '@data-client/graphql'; import { User } from './User'; export class Article extends GQLEntity { title = ''; content = ''; author = User.fromJS(); tags: string[] = []; createdAt = Temporal.Instant.fromEpochMilliseconds(0); static schema = { author: User, createdAt: Temporal.Instant.from, }; }
static schema 以声明式的方式定义需要处理的字段。在这个例子中,author 是另一个需要提取的 Entity,而 createdAt 会从字符串转换为
Date
对象。
Entity 通过 query 或 mutate 的第二个参数绑定到 GQLEndpoint。
如下所示,覆盖其他静态成员可以自定义数据生命周期。
数据生命周期
方法
pk(parent?, key?, args?): string?
PK 是 primary key(主键)的缩写,旨在为任意 Entity 提供一种获取键标识符的标准方式。
GraphQL 使用 id 字段作为标准的全局对象标识符。
pk() {
return this.id;
}
static key: string
它定义的是 Entity 本身(而不是实例)的键。这个值必须全局唯一。
它默认为 this.name;但在会修改类名的生产构建中,这可能会失效。这通常被称为类名混淆。
这种情况下,你可以覆盖 key,或者禁用类名混淆。
class User extends GQLEntity {
username = '';
static key = 'User';
}
static process(input, parent, key, args): processedEntity
在该 Entity 规范化开始时运行。返回值会保存到 store 中。
默认只是简单地复制响应({...input})
如何通过覆盖它来为关系型数据构建反向查找
static mergeWithStore(existingMeta, incomingMeta, existing, incoming): mergedValue
static mergeWithStore(
existingMeta: {
date: number;
fetchedAt: number;
},
incomingMeta: { date: number; fetchedAt: number },
existing: any,
incoming: any,
) {
const shouldUpdate = this.shouldUpdate(
existingMeta,
incomingMeta,
existing,
incoming,
);
if (shouldUpdate) {
// distinct types are not mergeable (like delete symbol), so just replace
if (typeof incoming !== typeof existing) {
return incoming;
} else {
return this.shouldReorder(
existingMeta,
incomingMeta,
existing,
incoming,
)
? this.merge(incoming, existing)
: this.merge(existing, incoming);
}
} else {
return existing;
}
}
在规范化过程中,如果 store 中已存在处理后的 Entity,就会调用 mergeWithStore()。
它会调用 shouldUpdate()、shouldReorder(),并可能调用 merge()
static shouldUpdate(existingMeta, incomingMeta, existing, incoming): boolean
static shouldUpdate(
existingMeta: { date: number; fetchedAt: number },
incomingMeta: { date: number; fetchedAt: number },
existing: any,
incoming: any,
) {
return existingMeta.fetchedAt <= incomingMeta.fetchedAt;
}
阻止更新
shouldUpdate 也可以用来短路 Entity 的更新。
import deepEqual from 'deep-equal';
class Article extends GQLEntity {
title = '';
content = '';
published = false;
static shouldUpdate(
existingMeta: { date: number; fetchedAt: number },
incomingMeta: { date: number; fetchedAt: number },
existing: any,
incoming: any,
) {
return !deepEqual(incoming, existing);
}
}
static shouldReorder(existingMeta, incomingMeta, existing, incoming): boolean
static shouldReorder(
existingMeta: { date: number; fetchedAt: number },
incomingMeta: { date: number; fetchedAt: number },
existing: any,
incoming: any,
) {
return incomingMeta.fetchedAt < existingMeta.fetchedAt;
}
返回 true 时,会调换 merge 中传入 Entity 与 store 中 Entity 的参数顺序。使用默认的 merge 时,这会让已有 Entity 的字段覆盖传入 Entity 的字段,而不是反过来。
示例
import { GQLEntity } from '@data-client/graphql'; export class LatestPriceEntity extends GQLEntity { updatedAt = 0; price = '0.0'; symbol = ''; static shouldReorder( existingMeta: { date: number; fetchedAt: number }, incomingMeta: { date: number; fetchedAt: number }, existing: { updatedAt: number }, incoming: { updatedAt: number }, ) { return incoming.updatedAt < existing.updatedAt; } }
static merge(existing, incoming): mergedValue
static merge(existing: any, incoming: any) {
return {
...existing,
...incoming,
};
}
Merge 用于处理传入的 Entity 已经存在的情况。当同一个响应中出现相同的 Entity 时,会直接调用它。默认情况下,当 mergeWithStore() 判定传入的 Entity 应与 Reactive Data Client store 中已持久化的 Entity 合并时,也会调用它。
如何通过覆盖它来为关系型数据构建反向查找
static mergeMetaWithStore(existingMeta, incomingMeta, existing, incoming): meta
static mergeMetaWithStore(
existingMeta: {
expiresAt: number;
date: number;
fetchedAt: number;
},
incomingMeta: { expiresAt: number; date: number; fetchedAt: number },
existing: any,
incoming: any,
) {
return this.shouldReorder(existingMeta, incomingMeta, existing, incoming)
? existingMeta
: incomingMeta;
}
在规范化过程中,如果 store 中已存在处理后的 Entity,就会调用 mergeMetaWithStore()。
static queryKey(args, queryKey, getEntity, getIndex): pk?
这个方法让 Entities 成为 Queryable——无需 endpoint 即可访问 store。
覆盖它可以自定义这一行为,或者完全禁用它。
返回 undefined 会禁用这一行为。
返回 pk 字符串则会尝试查找该 Entity 并在响应中使用。
使用时,过期策略会根据该 Entity 自身的元数据计算。
static createIfValid(processedEntity): Entity | undefined
在反规范化 Entity 时调用。如果它被判定为“有效”,就会创建该类的实例。
返回 undefined 会导致 Invalid 过期状态,就像 Invalidate 一样。
Invalid 过期通常意味着 hook 会进入加载状态并尝试重新获取。
static createIfValid(props): AbstractInstanceType<this> | undefined {
if (this.validate(props)) {
return undefined as any;
}
return this.fromJS(props);
}
static validate(processedEntity): errorMessage?
在规范化和反规范化时都会运行。返回字符串表示出错(该字符串即错误消息)。
规范化期间,校验失败会导致这次获取出错。
反规范化期间,校验失败会把该结果标记为“无效”,因此会阻塞直到获取到结果。
默认仅在开发模式下做一些基本的字段存在性检查。覆盖它即可禁用或自定义。
static fromJS(props): Entity
把 props 复制到新实例的工厂方法。请用它代替 new MyEntity(),以确保默认的 props 被覆盖。
字段
static schema: { [k: keyof this]: Schema }
定义关联 Entity 成员,或者 Date、BigNumber 这类字段反序列化。
{"post":{"id":"5","author":{"id":"123","name":"Jim"},"content":"Happy day","createdAt":"2019-01-23T06:07:48.311Z"}}
import { GQLEntity } from '@data-client/graphql'; import { Temporal } from 'temporal-polyfill'; import { User } from './User'; export class Post extends GQLEntity { author = User.fromJS({}); createdAt = Temporal.Instant.fromEpochMilliseconds(0); content = ''; title = ''; static schema = { author: User, createdAt: Temporal.Instant.from, }; static key = 'Post'; }
可选成员
这里引用的 Entity 中,如果在 Record 定义本身中有默认值,则被视为“可选”的
class User extends GQLEntity {
friend: User | null = null; // this field is optional
lastUpdated = Temporal.Instant.fromEpochMilliseconds(0);
static schema = {
friend: User,
lastUpdated: Temporal.Instant.from,
};
}
static indexes?: (keyof this)[]
索引可以提升基于这些参数进行查找时的性能。把之后想作为参数用来查找的字段名(例如 slug、username)添加到这个列表中。
不要把 id 这样的主键添加到索引列表中,因为它已经被优化过了。