Saltar al contenido principal

v0.15: Vue 3, Collection.remove, actualización de useDebounce

· 10 min de lectura
Nathaniel Tucker
Creator of Reactive Data Client

Nuevas plataformas:

  • Vue 3 con composables completos: useSuspense, useCache, useDLE, useController, useQuery

Nuevas funcionalidades:

Rendimiento:

Cambios incompatibles:

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:

main.ts
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:

ArticleDetail.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:

Pruebas con MockPlugin​

test setup
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

Before
// 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
After
// 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 endpoints
  • collapseFixture - 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

Benchmarks a lo largo del tiempo | Ver benchmark

Otras mejoras​

  • Se corrigen los tipos de getPage cuando 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 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

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

Before
import { useDebounce } from '@data-client/react';

const debouncedQuery = useDebounce(query, 100);
After
import { useDebounce } from '@data-client/react';

const [debouncedQuery] = useDebounce(query, 100);
// Or use the pending state
const [debouncedQuery, isPending] = useDebounce(query, 100);
consejo

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

Before
import { MemoCache, denormalize } from '@data-client/normalizr';

const memo = new MemoCache();
// Auto-detected ImmutableJS
After
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()​

Before
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);
}
After
normalize(
input: any,
parent: any,
key: string | undefined,
args: any[],
visit: (...args: any) => any,
delegate: INormalizeDelegate,
): string {
delegate.mergeEntity(this, id, processedEntity);
}

queryKey()​

Before
queryKey(args, queryKey, getEntity, getIndex) {
getIndex(schema.key, indexName, value)[value];
getEntity(this.key, id);
return queryKey(this.schema, args, getEntity, getIndex);
}
After
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():

Before
const entities = delegate.getEntity(this.key);
if (entities)
Object.keys(entities).forEach(collectionPk => {
if (!filterCollections(JSON.parse(collectionPk))) return;
delegate.mergeEntity(this, collectionPk, normalizedValue);
});
After
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

Before
import { INVALID } from '@data-client/endpoint';

normalize(..., delegate) {
delegate.setEntity(this, pk, INVALID);
}

queryKey(args, unvisit, delegate) {
if (!found) return INVALID;
}
After
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

Before
if (action.key in this.fetched)
After
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​

Before
this.memo.buildQueryKey(schema, args, state.entities, state.indexes, key);
this.memo.query(schema, args, state.entities, state.indexes);
After
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 Chat o reportar un error