跳到主要内容

GQLEntity

GraphQL 有一种标准方式来定义 pk,即使用 id 字段。

GQLEntity 自动带有一个 id 字段,用作 pk。

extends

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 自身的元数据计算。

默认使用第一个参数在 pk() 和 indexes 中查找

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?​

在规范化和反规范化时都会运行。返回字符串表示出错(该字符串即错误消息)。

规范化期间,校验失败会导致这次获取出错。

反规范化期间,校验失败会把该结果标记为“无效”,因此会阻塞直到获取到结果。

默认仅在开发模式下做一些基本的字段存在性检查。覆盖它即可禁用或自定义。

对字段不完整的 endpoint 使用校验

static fromJS(props): Entity​

把 props 复制到新实例的工厂方法。请用它代替 new MyEntity(),以确保默认的 props 被覆盖。

字段​

static schema: { [k: keyof this]: Schema }​

定义关联 Entity 成员,或者 Date、BigNumber 这类字段反序列化。

Fixtures
query getPost($id: ID!) { post(id: $id) { id author createdAt content title } } {"id":"123"}
{"post":{"id":"5","author":{"id":"123","name":"Jim"},"content":"Happy day","createdAt":"2019-01-23T06:07:48.311Z"}}
▶User
▶Post
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';
}
▶PostPage
结果
Store▶

可选成员​

这里引用的 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 这样的主键添加到索引列表中,因为它已经被优化过了。