Entity e normalização de dados
Entities têm uma chave primária. Isso permite acesso fácil por meio de uma tabela de consulta. Assim, é simples encontrar, atualizar, criar ou excluir os mesmos dados, não importa em qual endpoint eles foram usados.
- State
- Response
- Endpoint
- Entity
- Component

[
{ "id": 1, "title": "this is an entity" },
{ "id": 2, "title": "this is the second entity" }
]
const getPresentations = new Endpoint(
() => fetch(`/presentations`).then(res => res.json()),
{ schema: new Collection([Presentation]) },
);
class Presentation extends Entity {
id = '';
title = '';
static key = 'Presentation';
}
import { useSuspense } from '@data-client/react';
import { getPresentations } from './api/Presentation';
export function PresentationsPage() {
const presentation = useSuspense(getPresentations);
return presentation.map(presentation => (
<div key={presentation.pk()}>{presentation.title}</div>
));
}
Extrair entities de uma resposta é conhecido como normalização (normalization). Acessar uma resposta reverte
o processo por meio da desnormalização (denormalization).
Usar entities estende a garantia de igualdade referencial global do Reactive Data Client para além da granularidade de uma resposta inteira de endpoint.
Mutações e dados dinâmicos
Quando um endpoint altera dados, isso é conhecido como efeito colateral. Marcar um endpoint com sideEffect: true informa ao Reactive Data Client que esse endpoint não é idempotente e, portanto, não deve ser permitido em hooks que podem chamar o endpoint um número arbitrário de vezes, como useSuspense() ou useFetch()
Ao incluir os dados alterados na resposta do endpoint, o Reactive Data Client consegue atualizar quaisquer entities que extrai por meio do schema especificado.
- Create
- Update
- Delete
import { RestEndpoint, schema } from '@data-client/rest';
const todoCreate = new RestEndpoint({
urlPrefix: 'https://jsonplaceholder.typicode.com',
path: '/todos',
method: 'POST',
schema: new Collection([Todo]).push,
});
Exemplo de uso
import { useController } from '@data-client/react';
import { todoCreate } from './api/Todo';
import Form from './Form';
import FormField from './FormField';
export default function NewTodoForm() {
const ctrl = useController();
return (
<Form
onSubmit={e => ctrl.fetch(todoCreate, new FormData(e.target))}
>
<FormField name="title" />
</Form>
);
}
import { RestEndpoint } from '@data-client/rest';
const todoUpdate = new RestEndpoint({
urlPrefix: 'https://jsonplaceholder.typicode.com',
path: '/todos/:id',
method: 'PUT',
schema: Todo,
});
Exemplo de uso
import { useController, useSuspense } from '@data-client/react';
import { todoDetail, todoUpdate } from './api/Todo';
import Form from './Form';
import FormField from './FormField';
export default function UpdateTodoForm({ id }: { id: number }) {
const todo = useSuspense(todoDetail, { id });
const ctrl = useController();
return (
<Form
onSubmit={e =>
ctrl.fetch(todoUpdate, { id }, new FormData(e.target))
}
initialValues={todo}
>
<FormField name="title" />
</Form>
);
}
import { Invalidate, RestEndpoint } from '@data-client/rest';
const todoDelete = new RestEndpoint({
urlPrefix: 'https://jsonplaceholder.typicode.com',
path: '/todos/:id',
method: 'DELETE',
schema: new Invalidate(Todo),
});
Exemplo de uso
import { useController } from '@data-client/react';
import { todoDelete, type Todo } from './api/Todo';
export default function TodoWithDelete({ todo }: { todo: Todo }) {
const ctrl = useController();
return (
<div>
{todo.title}
<button onClick={() => ctrl.fetch(todoDelete, { id: todo.id })}>
Delete
</button>
</div>
);
}
As mutações atualizam automaticamente o cache normalizado, resultando em dados consistentes e atualizados.
Schema
Schemas são uma definição declarativa de como processar respostas
- onde esperar Entities
- Funções para desserializar campos
import { RestEndpoint, Collection } from '@data-client/rest';
const getTodoList = new RestEndpoint({
urlPrefix: 'https://jsonplaceholder.typicode.com',
path: '/todos',
schema: new Collection([Todo]),
});
Colocar nossa Entity Todo em uma Collection de array nos permite
adicionar com push ou com unshift novos Todos a ela com facilidade.
Além do array, há alguns outros 'schemas' disponíveis para vários padrões. Os dois primeiros (Object e Array) têm atalhos que usam literais de objeto e de array.
| Tipo de dado | Mutável | Schema | Descrição | Queryable |
|---|---|---|---|---|
| Objeto | ✅ | Entity | um único objeto único | ✅ |
| ✅ | Union(Entity) | objetos polimórficos (A | B) | ✅ | |
| 🛑 | Object | chaves conhecidas estaticamente | 🛑 | |
| Invalidate(Entity) | excluir uma entity | 🛑 | ||
| Lista | ✅ | Collection(Array) | listas expansíveis | ✅ |
| 🛑 | Array | listas imutáveis | 🛑 | |
| All | lista de todas as entities de um tipo | ✅ | ||
| Mapa | ✅ | Collection(Values) | mapas expansíveis | ✅ |
| 🛑 | Values | mapas imutáveis | 🛑 | |
| Scalar | ✅ | Scalar | campos de entity dependentes de lens | ✅ |
| qualquer | Query(Queryable) | transformações personalizadas memoizadas | ✅ | |
| Lazy(Schema) | desnormalização adiada | ✅ |
Aninhamento
Além disso, as próprias Entities podem especificar schemas aninhados por meio de um membro static schema.
- Entity
- Response
import { Entity } from '@data-client/endpoint';
class Todo extends Entity {
id = 0;
user = User.fromJS();
title = '';
completed = false;
static key = 'Todo';
static schema = {
user: User,
};
}
class User extends Entity {
id = 0;
username = '';
static key = 'User';
}
{
"id": 5,
"user": {
"id": 10,
"username": "bob"
},
"title": "Write some Entities",
"completed": false
}
Representações de dados
Além disso, funções podem ser usadas como schema. Elas serão chamadas durante a desnormalização. Isso pode ser útil com representações como bignumber ou temporal instant
import { Entity } from '@data-client/endpoint';
class Todo extends Entity {
id = 0;
user = User.fromJS();
title = '';
completed = false;
dueDate = Temporal.Instant.fromEpochMilliseconds(0);
static key = 'Todo';
static schema = {
user: User,
dueDate: Temporal.Instant.from,
};
}
Graças à garantia de igualdade referencial global, a construção dos membros ocorre apenas uma vez por atualização.
Inspeção do store (depuração)
A extensão de navegador DevTools pode ser instalada para inspecionar e depurar o store.

Benchmarks
A memoização em nível de Entity entrega desempenho de desnormalização até 20x maior e propagação de mutações até 90x mais rápida em comparação com abordagens não normalizadas. Veja a página completa de Desempenho para os resultados dos benchmarks de normalização, bem como benchmarks completos da integração com React.