v0.19: Controller.set() em lote, TypeScript mais rápido
O original em inglês mudou depois desta tradução, então ela pode estar desatualizada.
A v0.19 permite que um Manager escreva um lote inteiro de linhas recebidas por stream em uma única atualização da store, verifica os tipos do código de endpoints cerca de 2x mais rápido e corrige uma rodada de problemas de TypeScript e Vue. Coding agents também ganham um plugin do Claude Code, skills de Vue e llms.txt; veja Data Client para Coding Agents.
Novas APIs:
- Controller.set() com schemas Array - Escreva muitas entidades em uma única atualização da store com
ctrl.set([Entity], rows), até 95x mais rápido do que umset()por linha
Desempenho:
- TypeScript mais rápido - Editores e o
tscverificam código com RestEndpoint e resource() cerca de 2x mais rápido com 40% menos memória, detectando todos os erros que detectavam antes
Outras melhorias:
- Corrige a resolução de módulos do TypeScript 7 para os
exportsdo pacote; os imports agora resolvem para os arquivos de declaração (#4019) - renderDataHook() executa os efeitos de montagem do provider quando a primeira renderização suspende (#4099)
- Os valores de Controller.set() são tipados pelo schema, então
ctrl.set(new schema.All(Todo), 42)é um erro de TypeScript (#4133) - Controller.set() exclui uma entidade com um schema Invalidate sem a gambiarra do lote de uma linha (#4230)
- process() recebe params tipados em
new RestEndpoint(),.extend()eresource().extend(), então um parâmetro de path errado é um erro de TypeScript em vez deundefinedem tempo de execução (#4183, #4184) - Os retornos de getOptimisticResponse() são tipados pelo schema, então um tipo de campo errado é um erro de TypeScript em vez de um flash de dados incorretos (#4198)
- useSuspense() e useLive() do Vue mantêm os dados anteriores enquanto novos argumentos carregam, em vez de retornar
undefined(#4131) - useSuspense() e useLive() do Vue enviam erros de fetch após a mudança de argumentos para
onErrorCaptured()em vez de uma rejeição de promise não tratada (#4135) - useSuspense(), useDLE() e useFetch() do Vue não buscam mais novamente dados desatualizados a cada atualização da store, então um
controller.set()não é sobrescrito (#4134) - useSuspense() do Vue mostra dados desatualizados na montagem e busca novamente em segundo plano, como no React, em vez de mostrar o fallback do
<Suspense>até a nova busca terminar (#4169) - useDLE() e useCache() do Vue mantêm dados
invalidIfStaleexpirados durante atualizações não relacionadas da store, em vez de ficarem travados carregando (#4142) - O
DataClientPlugindo Vue é instalado em versões do Vue anteriores à 3.5 em vez de lançarapp.onUnmount is not a function(#4146) - O DataClientPlugin do Vue faz coleta de lixo por padrão, igual ao DataProvider do React, então você não precisa mais passar
gcPolicy; como ele decide o que remover (#4148) - Os composables do Vue aceitam argumentos getter como
() => ({ id: props.id }); eles eram tipados para permiti-los, mas passavam a própria função ao endpoint (#4115) - Corrige os erros
Cannot find name 'NoInfer'eexport typeno TypeScript 4.x comskipLibCheckdesativado (#4138) - Corrige erros de tipo de
Entity,Endpoint,UnioneRestEndpointno TypeScript 4.0–4.5 comskipLibCheckdesativado (#4140) - Corrige erros
',' expectedao importar@data-client/react/redux,@data-client/react/nextjsou@data-client/testno TypeScript 4.0–4.4 (#4232) - Corrige
acterenderHookde@data-client/testtipados comoanyno TypeScript 4.0–4.4 (#4251) - Classes Entity podem ser usadas onde um
EntityInterfaceé esperado (#4149); prepare as sobrescritas depk()para um lançamento futuro - useCache() e useDLE() retornam
undefinedpara uma entidade excluída cuja nova busca falhou, em vez de umSymboltruthy que passava por verificaçõesif (!data); remova quaisquer gambiarras (#4150) - useFetch() do Vue impede que seus dados sejam coletados pelo garbage collector enquanto está montado, como o useSuspense(), então um
gcPolicyconfigurado não remove mais dados pré-carregados que componentes leem depois (#4152) - Apps com o Redux DevTools aberto não travam mais em desenvolvimento com stores grandes ou atualizações frequentes; cada atualização serializa 40-60x mais rápido (#4163)
- resource().extend() com um objeto de sobrescritas de endpoints mantém em seu tipo os endpoints adicionados antes com
.extend('name', options), então usá-los não é mais um erro de TypeScript (#4184) - RestEndpoint.push,
unshifteremoveaceitam os campos da Entity como body quando um Query ou Lazy envolve a Collection, em vez de apenasFormData(#4226) - useFetch() do Vue é tipado como o
Refsomente leitura que retorna, entãopromise.resolved(sempreundefined) agora é um erro de TypeScript; leiapromise.value.resolvedem vez disso (#4114) - Uma subclasse de
Controllerpassada ao DataProvider ou ao DataClientPlugin não precisa mais de um cast para passar na verificação de tipos (#4228) - A
storede prepareStore() pode ser passada ao<Provider>do react-redux sem cast, e temdispatch()em seu tipo (#4238) - resource().create aceita FormData sem cast, como
update,partialUpdateegetList.push(#4235) - RestEndpoint.push,
unshifteremoveenviam um body em array como JSON, então criar ou remover vários itens de uma vez não envia mais"[object Object]"ao seu servidor;resource().createtambém aceita um array (#4262) - O
createDataClient()do Vue está descontinuado, já que é interno ao DataClientPlugin; instale o plugin comapp.use(DataClientPlugin, options)em vez de chamá-lo diretamente (#4193) - O
waitForNextUpdate()dorenderDataCompose()do Vue está descontinuado, já que desiste silenciosamente após 1 segundo e deixa um teste passar enquanto ainda está suspenso; useawait resultem vez disso, ouawait allSettled()após uma mudança, que agora também espera as buscas iniciadas por uma mudança de prop (testes unitários de composables, #4221)
- TypeScript 4.0 ou posterior é obrigatório - as declarações para TypeScript 3.x foram removidas (#4151)
Prepare-se para lançamentos futuros:
- Tipe
argscomoreadonlynas sobrescritas deEntity.pk() - Use
DataClientPluginem vez docreateDataClient()do Vue - Substitua
waitForNextUpdate()nos testes de Vue
Controller.set() em lote
Um Manager que recebe um stream de linhas (como cotações de preços por um websocket) agora pode escrever
todas elas com um único Controller.set() passando um schema Array.
Antes da v0.19, [Ticker] era um erro de TypeScript, então a gambiarra comum era um set() por linha:
ws.onmessage = event => {
const rows = JSON.parse(event.data);
for (const row of rows) {
ctrl.set(Ticker, { product_id: row.product_id }, row);
}
};
ws.onmessage = event => {
const rows = JSON.parse(event.data);
ctrl.set([Ticker], rows);
};
Cada linha é mesclada com sua entidade armazenada, e as entidades que não estão na lista ficam intactas. Schemas Array não recebem args nem
função de atualização. Para agrupar em lote tipos de Entity diferentes, exclusões ou linhas indexadas por id, passe um schema Union,
Invalidate ou Values; veja
Controller.set() para exemplos.
#4103
Experimente os dois botões abaixo. Esta verificação no navegador parte de um store vazio e mede o tempo de um Promise.all com 500 chamadas de set()
em comparação com um único set() em lote. Ambos os caminhos resultam em um único commit do React, e cada um grava 500 preços novos.
import React from 'react'; import { useController, useQuery } from '@data-client/react'; import { Ticker, newPrices } from './Ticker'; function PriceStream() { const ctrl = useController(); const [timing, setTiming] = React.useState(''); const first = useQuery(Ticker, { product_id: 'COIN-0' }); const time = async ( label: string, write: (rows: ReturnType<typeof newPrices>) => Promise<unknown>, ) => { const rows = newPrices(); const start = performance.now(); await write(rows); setTiming(`${label}: ${(performance.now() - start).toFixed(1)} ms`); }; const perRow = () => time('500 set() calls', rows => Promise.all( rows.map(row => ctrl.set(Ticker, { product_id: row.product_id }, row)), ), ); // highlight-next-line const batch = () => time('1 batch set()', rows => ctrl.set([Ticker], rows)); return ( <div> <button onClick={perRow}>set() per row</button>{' '} <button onClick={batch}>batch set()</button> <p>COIN-0: {first ? `$${first.price}` : 'no data yet'}</p> <p>{timing}</p> </div> ); } render(<PriceStream />);
Desempenho
Cada set() é uma atualização separada da store, e toda atualização da store copia a tabela daquele tipo de entidade. Escrever as linhas uma
de cada vez repete essa cópia para cada linha, então o custo cresce tanto com o tamanho do lote quanto com o tamanho da store. Um lote
paga essa cópia uma única vez.
O gráfico é o benchmark setMany em node de
examples/benchmark/core.js:
uma store que já contém 500 entidades, depois um set() síncrono por linha comparado a um único set([Ticker], rows).
Ele mede a atualização da store: 20x mais rápido para 50 linhas e 95x mais rápido para 500 linhas.
#4103
Em um Manager, acumule as mensagens recebidas em um buffer e descarregue cada lote com um único set(), como descrito em
Agrupando atualizações de alta frequência em lote. O StreamManager do app de moedas agora
descarrega as mensagens de ticker da Coinbase dessa forma.
handleMessage(msg: any) {
if (msg.type in this.entities) {
(this.buffer[msg.type] ??= {})[msg.product_id] = msg;
this.flushTimeout ??= setTimeout(this.flush, 50);
}
}
flush = () => {
const buffer = this.buffer;
this.buffer = {};
this.flushTimeout = undefined;
for (const type in buffer) {
this.controller.set([this.entities[type]], Object.values(buffer[type]));
}
};
Explore o exemplo coin-app
Se você filtra as ações do DevToolsManager por schema, o action.schema de uma escrita em lote
é o schema Array, então compare action.schema[0] para [Ticker] em vez da própria Entity.
TypeScript mais rápido
O TypeScript verifica novamente seu código a cada tecla digitada no editor e a cada build de CI. Em apps com muitos endpoints,
tipos pesados de biblioteca aparecem como autocomplete lento, sublinhados vermelhos que levam segundos para aparecer e builds mais demorados.
A v0.19 torna RestEndpoint, resource() e .extend() muito mais baratos
de verificar, sem mudanças de código do seu lado. O TypeScript continua reportando todos os erros que reportava antes
(#4173).
Resultados
Nosso teste de estresse mais pesado, um arquivo com 150 RestEndpoints com paths longos, .extend() e .paginated(), agora é verificado
2x mais rápido no TypeScript 6 e 2,2x mais rápido no TypeScript 7, usando cerca de 40% menos memória. Todos os números comparam
os pacotes publicados da v0.18.1 com a v0.19; os tempos de verificação são o melhor de 10 execuções.
Código com muitos endpoints faz muito menos trabalho de tipos. Instanciações de tipo são a unidade de trabalho do TypeScript: elas são determinísticas, então podem ser comparadas com precisão entre máquinas. Union faz mais trabalho do que na v0.18, porque a v0.19 agora verifica os tipos dos valores de set() (veja abaixo).
Passe o mouse ou toque em uma célula para ver os números exatos.
| TS 6 check time | TS 7 check time | TS 6 memory | |
|---|---|---|---|
| Long paths 150 RestEndpoints with 6-param paths, .extend() and .paginated() | -48% 3.83s → 1.98s | -54% 1.5s → 0.69s | -42% 365MB → 211MB |
| Typical app A few resources with .extend(), .paginated() and hooks | same 0.42s → 0.42s | -15% 0.062s → 0.053s | +3% 106MB → 109MB |
| React hooks 40 resources through every hook, plus ctrl.fetch() and ctrl.set() | -10% 1.24s → 1.12s | -7% 0.41s → 0.38s | -6% 167MB → 157MB |
| Vue The same 40 resources through every composable | same 0.68s → 0.68s | -12% 0.17s → 0.15s | -4% 145MB → 139MB |
| 300 fields One Entity with 300 fields, read and updated 100 times | -14% 0.44s → 0.38s | -40% 0.086s → 0.052s | -9% 111MB → 101MB |
| Schemas All, Query, Invalidate, Array, Object and Collection | -3% 1.1s → 1.07s | -14% 0.36s → 0.31s | -4% 156MB → 150MB |
| Union A 30-member Union in a Collection and Values | -2% 0.42s → 0.41s | -4% 0.071s → 0.068s | -6% 107MB → 101MB |
| set() values 1000 ctrl.set() calls on a 30-member Union, a Collection of it and a 300-field Entity | -28% 1.49s → 1.07s | -54% 0.54s → 0.25s | -26% 176MB → 131MB |
| set() updaters 1000 ctrl.set(Union, args, prev => ...) updaters on a 30-member Union | -4% 3.58s → 3.43s | +11% 1.67s → 1.85s | +1% 381MB → 384MB |
As linhas de set() medem os valores tipados de set(), que a v0.18 não verificava de forma alguma. Valores simples ainda são verificados mais rápido do que antes, e funções de atualização em Unions grandes são verificadas em aproximadamente o mesmo tempo, mesmo que o TypeScript agora verifique o valor de retorno de cada função de atualização (#4179).
Arquivos pequenos são dominados pelo custo fixo de inicialização do TypeScript (carregar lib.dom.d.ts sozinho leva cerca de 100MB), então
seu tempo e memória quase não mudam. A economia se acumula conforme a base de código cresce.
O que mudou
.extend()e.paginated()são compartilhados entre todos os endpoints em vez de recriados para cada tipo de endpoint.- As opções de endpoint são inferidas como tipos de objeto simples, então o TypeScript para de reconstruí-las a cada uso.
- Parâmetros de path como
/users/:idsão lidos em uma única passagem.
Antes de publicar, desativamos todos os @ts-expect-error da suíte de testes no TypeScript 4.0 até o 7 e confirmamos
que todos os erros continuam aparecendo no mesmo lugar.
No TypeScript 5.x e anteriores, um método process(value, params) passado ao .extend() também não falha mais com
"implicitly has an 'any' type" em modo strict.
Outras melhorias
Valores tipados de set()
Controller.set() antes aceitava qualquer valor para um schema, então um erro de digitação ou um tipo de campo
errado só aparecia como dados incorretos em tempo de execução. Os valores agora são tipados pelo schema: uma
Entity recebe seus campos, enquanto uma Collection, All ou
Array recebe uma lista de linhas. Todo campo é opcional, já que set() mescla com o que já está armazenado, e
números e strings são intercambiáveis assim como nas respostas de API.
#4133
Passe o mouse sobre os sublinhados vermelhos para ver cada erro.
import type { Controller } from '@data-client/react'; import { schema } from '@data-client/rest'; import { Todo, TodoResource } from './Todo'; export function updateTodos(ctrl: Controller) { // ✅ rows are partial Todos; ids may be strings or numbers ctrl.set(TodoResource.getList.schema, [{ id: '5', completed: true }]); ctrl.set(Todo, { id: 5 }, todo => ({ completed: !todo.completed })); ctrl.set([Todo], [{ id: 1, title: 'first' }, { id: 2 }]); // ❌ All takes a list of rows ctrl.set(new schema.All(Todo), 42); // ❌ completed is a boolean ctrl.set(Todo, { id: 5 }, { id: 5, completed: 'yes' }); // ❌ Todo has no done field ctrl.set(TodoResource.getList.schema, [{ id: 5, done: true }]); // ❌ updaters must return Todo fields ctrl.set(Todo, { id: 5 }, todo => ({ title: todo.completed })); }
Um Query recebe a entrada do schema que envolve, já que set() normaliza esse schema em vez de
reverter o process(). Quando cada membro declara seu discriminador como um literal (como readonly type = 'post'), uma
linha de Union é verificada contra o membro que ela seleciona, então não pode misturar campos de membros diferentes; veja Unions.
Se um código que antes compilava agora falha aqui, ele estava escrevendo dados que seu schema não descreve. Corrija o valor, ou amplie os tipos dos campos da Entity se os dados realmente puderem ter essa forma.
Exclua uma entidade com set()
Passar um schema Invalidate ao Controller.set() exclui a entidade que sua linha identifica, mas a forma singular era um erro de TypeScript, então o código a envolvia em um lote de uma linha. Agora ela passa na verificação de tipos, com a linha tipada pelos campos da Entity (#4230):
ctrl.set([new Invalidate(Todo)], [{ id: '5' }]);
ctrl.set(new Invalidate(Todo), { id: '5' });
Como o set() em lote, ela não recebe args nem função de atualização.
Params tipados em process()
Um método process() passado a new RestEndpoint(),
.extend() ou resource().extend() recebia params tipado como
any, então ler um parâmetro que o endpoint não tem compilava e retornava undefined em tempo de execução. params e
body agora são tipados a partir do endpoint, incluindo um path definido na mesma chamada de .extend()
(#4183, #4184):
import { RestEndpoint } from '@data-client/rest'; const getUser = new RestEndpoint({ path: '/users/:id' }); export const getUserById = getUser.extend({ path: '/users/by-id/:userId', process(value, params) { const userId: string | number = params.userId; // @ts-expect-error 'id' is not a param of '/users/by-id/:userId' params.id; return { ...value, userId }; }, });
Quando todos os params são opcionais, params pode ser undefined, então leia-o com params?.page.
Isso pode revelar novos erros de TypeScript em métodos process() existentes. Cada um marca uma leitura que poderia ser undefined
ou lançar uma exceção em tempo de execução, então corrija o nome do param ou adicione a verificação que falta.
Respostas otimistas tipadas
getOptimisticResponse() podia retornar qualquer coisa, a menos que o endpoint tivesse
um process() tipado, então um tipo de campo errado só aparecia como dados incorretos na interface até o servidor responder. O valor de retorno
agora é verificado contra o schema do endpoint, como um valor tipado de set(): uma Entity recebe qualquer um de seus
campos, e uma Collection recebe uma lista deles
(#4198):
import { Entity, RestEndpoint } from '@data-client/rest'; class Todo extends Entity { id = 0; title = ''; completed = false; } export const toggleTodo = new RestEndpoint({ path: '/todos/:id', method: 'PATCH', schema: Todo, // @ts-expect-error completed is a boolean; 'false' is truthy, so the checkbox would stay checked getOptimisticResponse(snap, { id }) { const todo = snap.get(Todo, { id }); if (!todo) throw snap.abort; return { id, completed: todo.completed ? 'false' : 'true' }; }, });
Isso pode revelar novos erros de TypeScript em métodos getOptimisticResponse() existentes. Cada um marca um valor que seria
armazenado com o tipo errado até o servidor responder, então retorne o tipo real do campo (aqui, !todo.completed).
push() tipado em Collections ordenadas
Envolver uma Collection em um Query para ordená-la ou filtrá-la fazia
push, unshift e remove rejeitarem qualquer body exceto FormData, embora adicionem à
lista em tempo de execução. O body agora é tipado a partir da Entity da Collection, então o cast pode ser removido e um campo errado é um
erro de TypeScript. O mesmo vale para uma Collection envolvida em Lazy
(#4226).
import { useController } from '@data-client/react';
import { getPosts } from './getPosts';
export function useAddPost() {
const ctrl = useController();
return (title: string, author: string) =>
ctrl.fetch(getPosts.push, { group: 'react' }, { title, author } as any);
}
import { useController } from '@data-client/react';
import { getPosts } from './getPosts';
export function useAddPost() {
const ctrl = useController();
return (title: string, author: string) =>
ctrl.fetch(getPosts.push, { group: 'react' }, { title, author });
}
onde getPosts ordena sua Collection com um Query:
import { Collection, Entity, Query, RestEndpoint } from '@data-client/rest';
export class Post extends Entity {
id = '';
title = '';
group = '';
author = '';
}
export const getPosts = new RestEndpoint({
path: '/:group/posts',
searchParams: {} as { orderBy?: 'title' | 'author' },
schema: new Query(
new Collection([Post], { nonFilterArgumentKeys: /orderBy/ }),
(posts, { orderBy }: { orderBy?: 'title' | 'author' } = {}) =>
orderBy ?
[...posts].sort((a, b) => a[orderBy].localeCompare(b[orderBy]))
: posts,
),
});
FormData em resource().create
O create de resource() envia um body FormData como está, assim como update, partialUpdate e
getList.push, mas seu tipo só permitia um objeto, então enviar um formulário exigia um cast
(#4235). Remova o cast:
<form
onSubmit={e => {
e.preventDefault();
ctrl.fetch(PostResource.create, new FormData(e.currentTarget) as any);
}}
>
<form
onSubmit={e => {
e.preventDefault();
ctrl.fetch(PostResource.create, new FormData(e.currentTarget));
}}
>
create está descontinuado; getList.push é seu substituto e também aceita FormData.
Argumentos getter no Vue
Composables do Vue como useSuspense() e useLive() eram tipados para aceitar uma
função getter como argumento, mas passavam a própria função ao endpoint em vez de chamá-la. Getters agora
funcionam da mesma forma que refs e computed, e o composable busca novamente quando qualquer coisa que o getter lê muda
(#4115). Remova o computed() ao redor se você só o usava para tornar
os argumentos reativos:
import { computed } from 'vue';
const props = defineProps<{ id: number }>();
const article = await useSuspense(
ArticleResource.get,
computed(() => ({ id: props.id })),
);
const props = defineProps<{ id: number }>();
const article = await useSuspense(ArticleResource.get, () => ({
id: props.id,
}));
Isso vale para todo composable que recebe argumentos de endpoint: useSuspense(), useLive(), useCache(), useDLE(), useFetch(), useQuery() e useSubscription().
Vue faz coleta de lixo por padrão
O DataClientPlugin do Vue só removia dados não utilizados se você mesmo passasse um
gcPolicy. Agora o padrão é new GCPolicy(), o mesmo do DataProvider do React, então você pode
remover a opção, a menos que a personalize (#4148).
import { DataClientPlugin, GCPolicy } from '@data-client/vue';
app.use(DataClientPlugin, { gcPolicy: new GCPolicy() });
import { DataClientPlugin } from '@data-client/vue';
app.use(DataClientPlugin);
Os dados só são removidos quando nenhum componente montado os lê e o dobro do seu tempo de vida de frescor passou desde que foram buscados (no mínimo dois minutos). Uma varredura verifica a cada cinco minutos. Qualquer coisa que um componente leia novamente antes disso volta a estar em uso.
Um componente que monta após a remoção trata os dados como nunca buscados: o useSuspense() os busca
de novo, enquanto o useCache() retorna undefined. Para manter dados não utilizados por mais tempo ou varrer com menos frequência,
passe seu próprio gcPolicy.
Stale-while-revalidate no Vue
Quando um componente monta com dados que estão desatualizados mas ainda válidos, o
useSuspense() do Vue agora os renderiza imediatamente e busca novamente em segundo plano, como no React. Antes,
ele mostrava o fallback do <Suspense> até a nova busca terminar (#4169).
Classes Entity passam na verificação de tipos como EntityInterface
Funções auxiliares tipadas para aceitar qualquer Entity com EntityInterface rejeitavam classes Entity, porque
Entity.pk() tipava seus args como um array mutável. Agora é readonly any[], então isto
passa na verificação de tipos (#4149):
import { Entity } from '@data-client/rest'; import type { EntityInterface } from '@data-client/react'; class User extends Entity { id = ''; name = ''; } function entityName(schema: EntityInterface) { return schema.key; } entityName(User);
Se você sobrescreve static pk() e tipa args como um array mutável, ainda compila, mas um futuro lançamento com mudanças incompatíveis
exigirá readonly; atualize agora.
Entidades excluídas são lidas como undefined
Quando uma entidade era excluída e sua nova busca falhava, useCache() e useDLE()
(React e Vue) retornavam um Symbol interno como dados. Um Symbol é truthy, então a verificação comum de "ainda não carregou" o deixava
passar e o componente renderizava como se tivesse uma entidade
(#4150):
function TodoDetail({ id }: { id: number }) {
const todo = useCache(TodoResource.get, { id });
// a deleted todo used to get past this guard, so todo.title.trim() threw
if (!todo) return <TodoPlaceholder />;
return <h3>{todo.title.trim()}</h3>;
}
Agora data é undefined, de acordo com seu tipo e com Controller.get(). O mesmo vale para
Controller.getResponse() e
Controller.fetchIfStale(), por exemplo em Managers personalizados.
Se você contornou isso, a gambiarra pode ser removida. Uma simples verificação de truthiness basta:
function TodoDetail({ id }: { id: number }) {
const todo = useCache(TodoResource.get, { id });
if (!todo || typeof todo === 'symbol') return <TodoPlaceholder />;
return <h3>{todo.title.trim()}</h3>;
}
function TodoDetail({ id }: { id: number }) {
const todo = useCache(TodoResource.get, { id });
if (!todo) return <TodoPlaceholder />;
return <h3>{todo.title.trim()}</h3>;
}
Para mostrar algo específico quando a nova busca falhou (por exemplo, um 404 após a exclusão), leia error de
useDLE() em vez de inspecionar data.
Redux DevTools mais rápido
Com a extensão Redux DevTools aberta, toda atualização da store em desenvolvimento
serializa a store inteira para a extensão, e cada timestamp nela era formatado do jeito lento. Stores grandes ou
atualizações frequentes, como polling, dados em tempo real ou muitas chamadas de controller.set(), faziam a página travar. Cada atualização agora
serializa 40-60x mais rápido, e os timestamps continuam aparecendo como 10:42:07.123 AM
(#4163). Builds de produção não incluem o DevTools, então não são
afetados.
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.19.0 @data-client/rest@^0.19.0 @data-client/endpoint@^0.19.0 @data-client/core@^0.19.0 @data-client/vue@^0.19.0 @data-client/test@^0.19.0 @data-client/img@^0.19.0
yarn add @data-client/react@^0.19.0 @data-client/rest@^0.19.0 @data-client/endpoint@^0.19.0 @data-client/core@^0.19.0 @data-client/vue@^0.19.0 @data-client/test@^0.19.0 @data-client/img@^0.19.0
pnpm add @data-client/react@^0.19.0 @data-client/rest@^0.19.0 @data-client/endpoint@^0.19.0 @data-client/core@^0.19.0 @data-client/vue@^0.19.0 @data-client/test@^0.19.0 @data-client/img@^0.19.0
<script type="importmap">
{
"imports": {
"@data-client/react": "https://esm.sh/@data-client/react@^0.19.0",
"@data-client/rest": "https://esm.sh/@data-client/rest@^0.19.0",
"@data-client/endpoint": "https://esm.sh/@data-client/endpoint@^0.19.0",
"@data-client/core": "https://esm.sh/@data-client/core@^0.19.0",
"@data-client/vue": "https://esm.sh/@data-client/vue@^0.19.0",
"@data-client/test": "https://esm.sh/@data-client/test@^0.19.0",
"@data-client/img": "https://esm.sh/@data-client/img@^0.19.0"
}
}
</script>
TypeScript 4.0 ou posterior
Pule esta seção se você já usa TypeScript 4.0 ou posterior.
As declarações para TypeScript 3.x foram removidas, já que não passavam mais na verificação de tipos em nenhuma versão do TypeScript 3.x. Atualize
typescript para ^4.0.0 ou posterior nas suas devDependencies. #4151
Prepare-se para lançamentos futuros
Nada aqui é necessário para atualizar para a 0.19; seu código continua funcionando como está. Cada item vai quebrar em um lançamento futuro, então atualizar agora torna aquela atualização trivial.
Os args de Entity.pk() são readonly
Pule isto se você não sobrescreve static pk() com um parâmetro args tipado.
Entity.pk() agora tipa args como readonly any[]. Sobrescritas que o anotam como um array
mutável ainda compilam por enquanto, mas um lançamento futuro exigirá readonly.
class User extends Entity {
static pk(value: any, parent?: any, key?: string, args?: any[]) {
return `${value.id}-${args?.[0]?.org}`;
}
}
class User extends Entity {
static pk(value: any, parent?: any, key?: string, args?: readonly any[]) {
return `${value.id}-${args?.[0]?.org}`;
}
}
createDataClient() do Vue
Pule isto se você instala a store com app.use(DataClientPlugin).
createDataClient() e ProvidedDataClient são internos ao DataClientPlugin e
deixarão de ser exportados. Em vez disso, instale o plugin e leia o controller com
useController(). #4193
import { createDataClient } from '@data-client/vue';
const { controller } = createDataClient({ managers, initialState });
import { DataClientPlugin, useController } from '@data-client/vue';
app.use(DataClientPlugin, { managers, initialState });
// in a component's setup()
const controller = useController();
waitForNextUpdate() do renderDataCompose() do Vue
Pule isto se seus testes de Vue não chamam waitForNextUpdate().
O waitForNextUpdate() de renderDataCompose() desiste
silenciosamente após 1 segundo, então um teste pode passar enquanto o composable ainda está suspenso. Ele será removido. Em vez disso, use await no
resultado e chame allSettled() após mudar props ou chamar o controller; agora ele também espera a
busca iniciada por uma mudança de prop. #4221
const { result, waitForNextUpdate } = await renderDataCompose(
(props: { id: number }) =>
useSuspense(ArticleResource.get, () => ({ id: props.id })),
{ props, resolverFixtures },
);
await waitForNextUpdate();
const article = await result;
props.id = 6;
await waitForNextUpdate();
const { result, allSettled } = await renderDataCompose(
(props: { id: number }) =>
useSuspense(ArticleResource.get, () => ({ id: props.id })),
{ props, resolverFixtures },
);
const article = await result;
props.id = 6;
await allSettled();
Suporte à atualização
Como sempre, se você tiver qualquer problema ou dúvida, fique à vontade para entrar no nosso ou abrir um bug
