v0.15: Vue 3, Collection.remove, actualización de useDebounce
Nuevas plataformas:
- Vue 3 con composables completos: useSuspense, useCache, useDLE, useController, useQuery
Nuevas funcionalidades:
- Collection.remove para quitar elementos de las collections
- RestEndpoint.remove para combinar PATCH con la eliminación de la collection
- Las Unions pueden hacer query() sin discriminador de tipo
- Invalidate admite Unions para operaciones de borrado polimórficas
- mockInitialState() para una configuración de pruebas más simple
Rendimiento:
- Mejora del 10-20% en las operaciones get/denormalize
- useDebounce() devuelve [val, isPending]
- El soporte de ImmutableJS se movió a las exportaciones /imm
- Consolidación de la interfaz delegate de Schema
Actualiza con un codemod automatizado para los cambios incompatibles más comunes:
npx jscodeshift -t https://dataclient.io/codemods/v0.15.js --extensions=ts,tsx,js,jsx src/
Soporte para Vue 3
El nuevo paquete @data-client/vue trae toda la potencia de Reactive Data Client a Vue 3 con composables nativos. #3549, #3585
Instalación
npm install @data-client/vue @data-client/rest
Configuración
Instala DataClientPlugin en tu app:
import { createApp } from 'vue';
import { DataClientPlugin } from '@data-client/vue';
const app = createApp(App);
app.use(DataClientPlugin);
app.mount('#app');
Composables
Todos los hooks conocidos están disponibles como composables de Vue:
<script setup lang="ts">
import { useSuspense } from '@data-client/vue';
import { ArticleResource } from './resources';
const props = defineProps<{ id: string }>();
const article = await useSuspense(
ArticleResource.get,
computed(() => ({ id: props.id })),
);
</script>
<template>
<article>
<h1>{{ article.title }}</h1>
<p>{{ article.content }}</p>
</article>
</template>
Composables disponibles:
useSuspense()- Obtención de datos con SuspenseuseCache()- Lee de la caché sin obtener datosuseDLE()- Patrón Data/Loading/Error sin Suspense #3592useController()- Acceso al controller para mutacionesuseQuery()- Consultas directas de schemas
Pruebas con MockPlugin
import { createApp } from 'vue';
import { DataClientPlugin } from '@data-client/vue';
import { MockPlugin } from '@data-client/vue/test';
const app = createApp(App);
app.use(DataClientPlugin);
app.use(MockPlugin, {
fixtures: [
{
endpoint: MyResource.get,
args: [{ id: 1 }],
response: { id: 1, name: 'Test' },
},
],
});
Collection.remove
Nuevo schema para quitar elementos de las Collections sin eliminar la entidad en sí. #3560
import { schema } from '@data-client/rest';
const TodoCollection = new schema.Collection([Todo]);
const TodoResource = resource({
path: '/todos/:id',
schema: Todo,
}).extend({
getList: { schema: TodoCollection },
});
// Remove from collection without deleting
ctrl.set(TodoResource.getList.schema.remove, { id: '123' });
Es útil cuando un elemento debe quitarse de una vista de lista pero seguir existiendo en otros lugares:
// Remove user from a specific group list
ctrl.set(UserResource.getList.schema.remove, { id: userId, group: 'admins' });
RestEndpoint.remove
RestEndpoint.remove combina una actualización PATCH con la eliminación de la collection; perfecto para operaciones de "archivar" o "mover". #3623
const getTodos = new RestEndpoint({
path: '/todos',
schema: new schema.Collection([Todo]),
});
// Removes Todo from collection AND updates it with new data
await ctrl.fetch(
getTodos.remove,
{},
{ id: '123', title: 'Done', completed: true, archived: true }
);
// Move user between groups
await ctrl.fetch(
UserResource.getList.remove,
{ group: 'team-a' },
{ id: 2, username: 'bob', group: 'team-b' }
);
// User removed from 'team-a' list AND entity updated with group: 'team-b'
Consultas de Union
Las Unions ahora pueden consultarse sin especificar el discriminador de tipo. #3558
// Type discriminator required
// @ts-expect-error - missing 'type'
const event = useQuery(EventUnion, { id });
// event is undefined
const newsEvent = useQuery(EventUnion, { id, type: 'news' });
// newsEvent is found
// Works without type discriminator
const event = useQuery(EventUnion, { id });
// event is found!
const newsEvent = useQuery(EventUnion, { id, type: 'news' });
// newsEvent is also found
Invalidate admite Unions
schema.Invalidate ahora acepta schemas Union para operaciones de borrado polimórficas. Además, resource().delete envuelve automáticamente los schemas Union con Invalidate. #3559
const FeedUnion = new schema.Union(
{ posts: Post, comments: Comment },
'type',
);
// resource() automatically handles Union delete
const FeedResource = resource({
path: '/feed/:id',
schema: FeedUnion,
});
await ctrl.fetch(FeedResource.delete, { id: '123' });
// For standalone endpoints, use schema.Invalidate directly
const deleteFeedItem = new RestEndpoint({
path: '/feed/:id',
method: 'DELETE',
schema: new schema.Invalidate(FeedUnion),
});
await ctrl.fetch(deleteFeedItem, { id: '123' });
Utilidades de prueba
mockInitialState()
Nueva utilidad para crear un estado prepoblado en las pruebas. a4092a1
import { mockInitialState } from '@data-client/react/mock';
import { ArticleResource } from './resources';
const state = mockInitialState([
{
endpoint: ArticleResource.get,
args: [{ id: 5 }],
response: { id: 5, title: 'Hello', content: 'World' },
},
]);
@data-client/core/mock
Nuevo punto de entrada de mocks con utilidades para construir implementaciones de mock personalizadas:
import {
MockController,
collapseFixture,
createFixtureMap,
} from '@data-client/core/mock';
MockController- Envoltorio del Controller para simular endpointscollapseFixture- Resuelve las respuestas de los fixtures (maneja respuestas en forma de función)createFixtureMap- Separa los fixtures en un mapa estático e interceptors
Rendimiento
Eliminar la sobrecarga de la detección automática de ImmutableJS y optimizar las rutas de código de desnormalización proporciona una mejora del 10-20% en las operaciones get/denormalize. #3421, #3468
Otras mejoras
- Se corrigen los tipos de
getPagecuando paginationField está en el body - Se corrige el manejo polimórfico de entidades invalidadas en schema.All()
- Se corrigen los tipos del argumento 'state' de controller.get y controller.getQueryMeta
Guía de migración
Esta actualización requiere actualizar todas las versiones de los paquetes simultáneamente.
- NPM
- Yarn
- pnpm
- esm.sh
npm install --save @data-client/react@^0.15.0 @data-client/rest@^0.15.0 @data-client/test@^0.15.0 @data-client/img@^0.15.0
yarn add @data-client/react@^0.15.0 @data-client/rest@^0.15.0 @data-client/test@^0.15.0 @data-client/img@^0.15.0
pnpm add @data-client/react@^0.15.0 @data-client/rest@^0.15.0 @data-client/test@^0.15.0 @data-client/img@^0.15.0
<script type="importmap">
{
"imports": {
"@data-client/react": "https://esm.sh/@data-client/react@^0.15.0",
"@data-client/rest": "https://esm.sh/@data-client/rest@^0.15.0",
"@data-client/test": "https://esm.sh/@data-client/test@^0.15.0",
"@data-client/img": "https://esm.sh/@data-client/img@^0.15.0"
}
}
</script>
Un codemod automatizado se encarga de los cambios incompatibles que se describen a continuación:
npx jscodeshift -t https://dataclient.io/codemods/v0.15.js --extensions=ts,tsx,js,jsx src/
useDebounce() devuelve [val, isPending]
useDebounce() ahora devuelve una tupla con el estado pendiente, siguiendo el patrón de useTransition de React 19. #3459
import { useDebounce } from '@data-client/react';
const debouncedQuery = useDebounce(query, 100);
import { useDebounce } from '@data-client/react';
const [debouncedQuery] = useDebounce(query, 100);
// Or use the pending state
const [debouncedQuery, isPending] = useDebounce(query, 100);
La firma anterior sigue disponible mediante @data-client/react/next para una migración gradual.
ImmutableJS se movió a las exportaciones /imm
El soporte de ImmutableJS ya no se detecta automáticamente. Usa las exportaciones explícitas de /imm para manejar estado de ImmutableJS. #3421, #3468
import { MemoCache, denormalize } from '@data-client/normalizr';
const memo = new MemoCache();
// Auto-detected ImmutableJS
import { MemoCache } from '@data-client/normalizr';
import { MemoPolicy, denormalize, normalize } from '@data-client/normalizr/imm';
const memo = new MemoCache(MemoPolicy);
El punto de entrada /imm ahora incluye tanto normalize() como denormalize() para un manejo completo del estado de ImmutableJS:
import { normalize } from '@data-client/normalizr/imm';
import { fromJS } from 'immutable';
const result = normalize(Article, responseData, args, {
entities: fromJS({}),
indexes: fromJS({}),
entitiesMeta: fromJS({}),
});
// result.entities is an ImmutableJS Map
Este cambio proporciona una mejora de rendimiento del 10-20% al eliminar la sobrecarga de la detección de ImmutableJS para quienes no lo usan.
Cambios en la interfaz delegate de Schema
Si tienes schemas personalizados que sobrescriben normalize() o queryKey(), los argumentos de callback se han consolidado en un objeto delegate. #3449
normalize()
normalize(
input: any,
parent: any,
key: string | undefined,
args: any[],
visit: (...args: any) => any,
addEntity: any,
getEntity: any,
checkLoop: any,
): string {
addEntity(this, processedEntity, id);
}
normalize(
input: any,
parent: any,
key: string | undefined,
args: any[],
visit: (...args: any) => any,
delegate: INormalizeDelegate,
): string {
delegate.mergeEntity(this, id, processedEntity);
}
queryKey()
queryKey(args, queryKey, getEntity, getIndex) {
getIndex(schema.key, indexName, value)[value];
getEntity(this.key, id);
return queryKey(this.schema, args, getEntity, getIndex);
}
queryKey(args, unvisit, delegate) {
delegate.getIndex(schema.key, indexName, value);
delegate.getEntity(this.key, id);
return unvisit(this.schema, args);
}
delegate.getEntity → delegate.getEntities #3501
Al iterar sobre todas las entidades de un tipo, usa delegate.getEntities(), que devuelve una interfaz restringida con los métodos iteradores keys() y entries():
const entities = delegate.getEntity(this.key);
if (entities)
Object.keys(entities).forEach(collectionPk => {
if (!filterCollections(JSON.parse(collectionPk))) return;
delegate.mergeEntity(this, collectionPk, normalizedValue);
});
const entities = delegate.getEntities(this.key);
if (entities)
for (const collectionKey of entities.keys()) {
if (!filterCollections(JSON.parse(collectionKey))) continue;
delegate.mergeEntity(this, collectionKey, normalizedValue);
}
Símbolo INVALID eliminado
El símbolo INVALID ya no se exporta. Usa en su lugar los métodos del delegate. #3461
import { INVALID } from '@data-client/endpoint';
normalize(..., delegate) {
delegate.setEntity(this, pk, INVALID);
}
queryKey(args, unvisit, delegate) {
if (!found) return INVALID;
}
normalize(..., delegate) {
delegate.invalidate({ key: this.key }, pk);
}
queryKey(args, unvisit, delegate) {
if (!found) return delegate.INVALID;
}
state.entityMeta -> state.entitiesMeta
Propiedad del estado interno renombrada por consistencia. Solo afecta al acceso directo al store. #3451
Cambios en la estructura de datos de NetworkManager
fetched, rejectors, resolvers y fetchedAt de NetworkManager
se han consolidado en fetching. #3394
if (action.key in this.fetched)
if (this.fetching.has(action.key))
Cambios en la API de MemoCache (normalizr)
Para quienes usan @data-client/normalizr directamente:
MemoCache.query() y buildQueryKey() reciben el estado como un solo argumento #3372
this.memo.buildQueryKey(schema, args, state.entities, state.indexes, key);
this.memo.query(schema, args, state.entities, state.indexes);
this.memo.buildQueryKey(schema, args, state, key);
this.memo.query(schema, args, state);
MemoCache.query() devuelve { data, paths }
const { data } = this.memo.query(schema, args, state);
return typeof data === 'symbol' ? undefined : (data as any);
Soporte para la actualización
Como siempre, si tienes algún problema o pregunta, no dudes en unirte a nuestro o reportar un error
