Entity y normalización de datos
Las Entities tienen una clave primaria. Esto permite un acceso sencillo mediante una tabla de búsqueda. Así resulta fácil encontrar, actualizar, crear o eliminar los mismos datos, sin importar en qué endpoint se hayan usado.
- 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';
}
<script setup lang="ts">
import { useSuspense } from '@data-client/vue';
import { getPresentations } from './api/Presentation';
const presentations = await useSuspense(getPresentations);
</script>
<template>
<div v-for="presentation in presentations" :key="presentation.pk()">
{{ presentation.title }}
</div>
</template>
Extraer entities de una respuesta se conoce como normalization. Acceder a una respuesta revierte
el proceso mediante denormalization.
Usar entities amplía la garantía de igualdad referencial global de Reactive Data Client más allá de la granularidad de una respuesta completa de un endpoint.
Mutaciones y datos dinámicos
Cuando un endpoint cambia datos, esto se conoce como efecto secundario. Marcar un endpoint con sideEffect: true le indica a Reactive Data Client que este endpoint no es idempotente y, por lo tanto, no debe permitirse en hooks que puedan llamar al endpoint un número arbitrario de veces, como useSuspense() o useFetch()
Al incluir los datos modificados en la respuesta del endpoint, Reactive Data Client puede actualizar cualquier entity que extraiga al especificar el schema.
- 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,
});
Ejemplo de uso
<script setup lang="ts">
import { useController } from '@data-client/vue';
import { todoCreate } from './api/Todo';
import Form from './Form.vue';
import FormField from './FormField.vue';
const ctrl = useController();
const handleSubmit = (e: Event) =>
ctrl.fetch(todoCreate, new FormData(e.target as HTMLFormElement));
</script>
<template>
<Form @submit="handleSubmit">
<FormField name="title" />
</Form>
</template>
import { RestEndpoint } from '@data-client/rest';
const todoUpdate = new RestEndpoint({
urlPrefix: 'https://jsonplaceholder.typicode.com',
path: '/todos/:id',
method: 'PUT',
schema: Todo,
});
Ejemplo de uso
<script setup lang="ts">
import { useController, useSuspense } from '@data-client/vue';
import { todoDetail, todoUpdate } from './api/Todo';
import Form from './Form.vue';
import FormField from './FormField.vue';
const props = defineProps<{ id: number }>();
const todo = await useSuspense(todoDetail, () => ({ id: props.id }));
const ctrl = useController();
const handleSubmit = (e: Event) =>
ctrl.fetch(
todoUpdate,
{ id: props.id },
new FormData(e.target as HTMLFormElement),
);
</script>
<template>
<Form @submit="handleSubmit" :initialValues="todo">
<FormField name="title" />
</Form>
</template>
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),
});
Ejemplo de uso
<script setup lang="ts">
import { useController } from '@data-client/vue';
import { todoDelete, type Todo } from './api/Todo';
defineProps<{ todo: Todo }>();
const ctrl = useController();
</script>
<template>
<div>
{{ todo.title }}
<button @click="ctrl.fetch(todoDelete, { id: todo.id })">Delete</button>
</div>
</template>
Las mutaciones actualizan automáticamente la caché normalizada, lo que da como resultado datos consistentes y actualizados.
Schema
Los schemas son una definición declarativa de cómo procesar las respuestas
- dónde esperar Entities
- Funciones para deserializar campos
import { RestEndpoint, Collection } from '@data-client/rest';
const getTodoList = new RestEndpoint({
urlPrefix: 'https://jsonplaceholder.typicode.com',
path: '/todos',
schema: new Collection([Todo]),
});
Colocar nuestra Entity Todo en una Collection de tipo array nos permite
agregar al final (push) o al inicio (unshift) nuevos Todos con facilidad.
Además del array, se proporcionan algunos 'schemas' más para diversos patrones. Los dos primeros (Object y Array) tienen atajos mediante literales de objeto y de array.
| Tipo de datos | Mutable | Schema | Descripción | Queryable |
|---|---|---|---|---|
| Object | ✅ | Entity | un solo objeto único | ✅ |
| ✅ | Union(Entity) | objetos polimórficos (A | B) | ✅ | |
| 🛑 | Object | claves conocidas estáticamente | 🛑 | |
| Invalidate(Entity) | eliminar una entidad | 🛑 | ||
| List | ✅ | Collection(Array) | listas ampliables | ✅ |
| 🛑 | Array | listas inmutables | 🛑 | |
| All | lista de todas las entidades de un tipo | ✅ | ||
| Map | ✅ | Collection(Values) | mapas ampliables | ✅ |
| 🛑 | Values | mapas inmutables | 🛑 | |
| Scalar | ✅ | Scalar | campos de entidad que dependen de la lente | ✅ |
| cualquiera | Query(Queryable) | transformaciones personalizadas memoizadas | ✅ | |
| Lazy(Schema) | desnormalización diferida | ✅ |
Anidamiento
Además, las propias Entities pueden especificar schemas anidados mediante un miembro 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
}
Representaciones de datos
Además, las funciones pueden usarse como schema. Se llamarán durante la desnormalización. Esto puede ser útil con representaciones como bignumber o 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,
};
}
Gracias a la garantía de igualdad referencial global, la construcción de los miembros solo ocurre una vez por actualización.
Inspección del store (depuración)
Se puede instalar la extensión de navegador DevTools para inspeccionar y depurar el store.

Benchmarks
La memoización a nivel de entity ofrece hasta 20x de rendimiento en la desnormalización y una propagación de mutaciones 90x más rápida en comparación con los enfoques no normalizados. Consulta la página completa de rendimiento para ver los resultados de los benchmarks de normalización, así como los benchmarks completos de la integración con React.