跳到主要内容

Invalidate

描述需要被标记为 INVALID 的 Entity。这会把条目从 collection 中移除,或者对那些需要该 Entity 的 endpoint 强制触发 suspense。

构造函数​

new Invalidate(entity)
new Invalidate(union)
new Invalidate(entityMap, schemaAttribute)
  • entity:要使其失效的单个 Entity。
  • union:用于多态失效的 Union schema。
  • entityMap:schema 键到 Entity 的映射。
  • schemaAttribute:可选(使用 entityMap 时必填)每个 Entity 上的一个属性,用于决定规范化时按照 entityMap 使用哪个 schema。可以是字符串或函数。如果是函数,它接收以下参数:
    • value:该 Entity 的输入值。
    • parent:输入数组的父对象。
    • key:输入数组在父对象上对应的键。

用法​

Fixtures
GET /users
[{"id":"123","name":"Jim"},{"id":"456","name":"Jane"},{"id":"555","name":"Phone"}]
DELETE /users/:id
▶api/User
import { Entity, RestEndpoint, Collection, Invalidate } from '@data-client/rest';

class User extends Entity {
  id = '';
  name = '';
}
export const getUsers = new RestEndpoint({
  path: '/users',
  schema: new Collection([User]),
});
export const deleteUser = new RestEndpoint({
  path: '/users/:id',
  method: 'DELETE',
  schema: new Invalidate(User),
});
▶UserPage
import { useSuspense, useController } from '@data-client/react';
import { getUsers, deleteUser } from './api/User';

function UsersPage() {
  const users = useSuspense(getUsers);
  const ctrl = useController();
  return (
    <div>
      {users.map(user => (
        <div key={user.pk()}>
          {user.name}{' '}
          <span
            style={{ cursor: 'pointer' }}
            onClick={() => ctrl.fetch(deleteUser, { id: user.id })}
          >
            ❌
          </span>
        </div>
      ))}
    </div>
  );
}
render(<UsersPage />);
结果
Store▶

批量失效​

这里我们通过把 Invalidate 包裹在数组中,再添加一个可一次删除多个 Entity 的 endpoint。这样 Data Client 就能对响应中的每个 Entity 执行 invalidate。

import { resource, Invalidate } from '@data-client/rest';
import Post from './Post';

export const PostResource = resource({
  schema: Post,
  path: '/posts/:id',
}).extend('deleteMany', {
  path: '/posts',
  body: [] as string[],
  method: 'DELETE',
  schema: [new Invalidate(Post)],
});
▶Usage
import { PostResource } from './Resource';
PostResource.deleteMany(['5', '13', '7']);
请求
DELETE /posts
content-type: application/json
Body: ["5","13","7"]
响应200 OK
[
{
"id": "5"
},
{
"id": "13"
},
{
"id": "7"
}
]

有时后端对 'DELETE' 请求什么也不返回。这种情况下,我们可以使用 process,根据参数 body 构造一个可用的响应。

import { resource, Invalidate } from '@data-client/rest';
import Post from './Post';

export const PostResource = resource({
  schema: Post,
  path: '/posts/:id',
}).extend('deleteMany', {
  path: '/posts',
  body: [] as string[],
  method: 'DELETE',
  schema: [new Invalidate(Post)],
  process(value, body) {
    // use the body payload to inform which entities to delete
    return body.map(id => ({ id }));
  }
});
▶Usage
import { PostResource } from './Resource';
PostResource.deleteMany(['5', '13', '7']);
请求
DELETE /posts
content-type: application/json
Body: ["5","13","7"]
响应204 No Content
NO CONTENT

如果要在没有 endpoint 的情况下删除多个 Entity(例如处理 websocket 消息时),可以把同样的 schema 传给 Controller.set():

ctrl.set([new Invalidate(Post)], [{ id: '5' }, { id: '13' }, { id: '7' }]);

或者删除单个 Entity:

ctrl.set(new Invalidate(Post), { id: '5' });

多态类型​

如果你的 endpoint 可以删除不止一种类型的 Entity,就可以使用多态失效。

使用 Union schema​

最简单的方式是直接传入已有的 Union schema:

import { Entity, RestEndpoint, Union, Invalidate } from '@data-client/rest';

class User extends Entity {
id = '';
name = '';
readonly type = 'users';
}
class Group extends Entity {
id = '';
groupname = '';
readonly type = 'groups';
}

const MemberUnion = new Union(
{ users: User, groups: Group },
'type'
);

const deleteMember = new RestEndpoint({
path: '/members/:id',
method: 'DELETE',
schema: new Invalidate(MemberUnion),
});

字符串 schemaAttribute​

或者,也可以用一个字符串属性内联定义多态映射:

import { RestEndpoint, Invalidate } from '@data-client/rest';

const deleteMember = new RestEndpoint({
path: '/members/:id',
method: 'DELETE',
schema: new Invalidate(
{ users: User, groups: Group },
'type'
),
});

函数 schemaAttribute​

返回值应与 entity map 中的某个键相匹配。这适用于更复杂的区分逻辑:

import { RestEndpoint, Invalidate } from '@data-client/rest';

const deleteMember = new RestEndpoint({
path: '/members/:id',
method: 'DELETE',
schema: new Invalidate(
{ users: User, groups: Group },
(input, parent, key) => input.memberType === 'user' ? 'users' : 'groups'
),
});

对 useSuspense() 的影响​

当 React 中当前正在展示的结果里有 Entity 失效时,useSuspense() 会将它们视为无效

  • 对于可选的 Entity,只是将其移除
  • 对于必需的 Entity,这会使整个响应失效,从而重新触发 suspense。