v0.15: Vue 3, Collection.remove, atualização do useDebounce
Novas plataformas:
- Vue 3 com composables completos: useSuspense, useCache, useDLE, useController, useQuery
Novos recursos:
- Collection.remove para remover itens de collections
- RestEndpoint.remove para combinar PATCH com remoção da collection
- Unions podem fazer query() sem o discriminador de tipo
- Invalidate dá suporte a Unions para operações de exclusão polimórficas
- mockInitialState() para uma configuração de testes mais simples
Desempenho:
- Melhoria de 10 a 20% nas operações de get/denormalize
- useDebounce() retorna [val, isPending]
- Suporte a ImmutableJS movido para os exports /imm
- Consolidação da interface de delegate dos Schemas
Atualize com um codemod automatizado para as mudanças incompatíveis mais comuns:
npx jscodeshift -t https://dataclient.io/codemods/v0.15.js --extensions=ts,tsx,js,jsx src/
Suporte a Vue 3
O novo pacote @data-client/vue traz todo o poder do Reactive Data Client para o Vue 3 com composables nativos. #3549, #3585
Instalação
npm install @data-client/vue @data-client/rest
Configuração
Instale o DataClientPlugin no seu app:
import { createApp } from 'vue';
import { DataClientPlugin } from '@data-client/vue';
const app = createApp(App);
app.use(DataClientPlugin);
app.mount('#app');
Composables
Todos os hooks já conhecidos estão disponíveis como composables do 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 disponíveis:
useSuspense()- Busca de dados integrada ao SuspenseuseCache()- Leitura do cache sem buscaruseDLE()- Padrão Data/Loading/Error sem Suspense #3592useController()- Acesso ao controller para mutaçõesuseQuery()- Consultas diretas a schemas
Testes com 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
Novo schema para remover itens de Collections sem excluir a entidade em si. #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' });
Isso é útil quando um item deve ser removido de uma visualização de lista, mas continuar existindo em outros lugares:
// Remove user from a specific group list
ctrl.set(UserResource.getList.schema.remove, { id: userId, group: 'admins' });
RestEndpoint.remove
RestEndpoint.remove combina uma atualização PATCH com a remoção da collection - perfeito para operações de "arquivar" ou "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 a Union
Unions agora podem ser consultadas sem especificar o 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 dá suporte a Unions
schema.Invalidate agora aceita schemas Union para operações de exclusão polimórficas. Além disso, resource().delete envolve automaticamente schemas Union com 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' });
Utilitários de teste
mockInitialState()
Novo utilitário para criar um estado pré-populado em testes. 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
Novo ponto de entrada de mock com utilitários para construir implementações de mock personalizadas:
import {
MockController,
collapseFixture,
createFixtureMap,
} from '@data-client/core/mock';
MockController- Wrapper de Controller para mockar endpointscollapseFixture- Resolve respostas de fixtures (trata respostas em forma de função)createFixtureMap- Separa fixtures em um mapa estático e interceptors
Desempenho
Remover a sobrecarga da detecção automática de ImmutableJS e otimizar os caminhos de código da desnormalização proporciona uma melhoria de 10 a 20% nas operações de get/denormalize. #3421, #3468
Outras melhorias
- Corrige os tipos de
getPagequando paginationField está no body - Corrige o tratamento polimórfico de entidades invalidadas em schema.All()
- Corrige os tipos do argumento 'state' de controller.get e controller.getQueryMeta
Guia de migração
Esta atualização exige atualizar todas as versões dos pacotes ao mesmo tempo.
- 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>
Um codemod automatizado trata as mudanças incompatíveis abaixo:
npx jscodeshift -t https://dataclient.io/codemods/v0.15.js --extensions=ts,tsx,js,jsx src/
useDebounce() retorna [val, isPending]
useDebounce() agora retorna uma tupla com o estado pendente, seguindo o padrão do useTransition do 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);
A assinatura anterior continua disponível via @data-client/react/next para uma migração gradual.
ImmutableJS movido para os exports /imm
O suporte a ImmutableJS não é mais detectado automaticamente. Use os exports explícitos /imm para lidar com estado em 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);
O ponto de entrada /imm agora inclui tanto normalize() quanto denormalize() para o tratamento completo de estado em 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
Essa mudança proporciona uma melhoria de desempenho de 10 a 20% ao remover a sobrecarga da detecção de ImmutableJS para quem não o utiliza.
Mudanças na interface de delegate dos Schemas
Se você tem schemas personalizados que sobrescrevem normalize() ou queryKey(), os argumentos de callback foram consolidados em um 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
Ao iterar sobre todas as entidades de um tipo, use delegate.getEntities(), que retorna uma interface restrita com os métodos iteradores keys() e 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 removido
O símbolo INVALID não é mais exportado. Use os métodos do delegate em seu lugar. #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
Propriedade do estado interno renomeada por consistência. Isso afeta apenas o acesso direto à store. #3451
Mudanças na estrutura de dados do NetworkManager
Os campos fetched, rejectors, resolvers e fetchedAt do NetworkManager
foram consolidados em fetching. #3394
if (action.key in this.fetched)
if (this.fetching.has(action.key))
Mudanças na API do MemoCache (normalizr)
Para quem usa @data-client/normalizr diretamente:
MemoCache.query() e buildQueryKey() recebem o state como um único 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() retorna { data, paths }
const { data } = this.memo.query(schema, args, state);
return typeof data === 'symbol' ? undefined : (data as any);
Suporte à atualização
Como sempre, se você tiver qualquer problema ou dúvida, fique à vontade para entrar no nosso ou registrar um bug
