Política de expiração do Endpoint
Por padrão, a política de cache do Reactive Data Client pode ser descrita como stale-while-revalidate. Isso significa que, quando há dados disponíveis, é possível evitar bloquear a aplicação usando os dados desatualizados. No entanto, em segundo plano, ele ainda atualizará os dados se forem antigos o suficiente.
Status de expiração
Fresh
Dados nesse estado são considerados novos o suficiente para que não precisem de fetch.
Stale
Os dados ainda podem ser exibidos, porém o Reactive Data Client pode tentar revalidá-los fazendo fetch novamente.
useSuspense() considera fazer fetch na montagem, bem como quando seus parâmetros mudam. Nesses casos, ele fará o fetch se os dados forem considerados desatualizados.
Invalid
Os dados não devem ser exibidos. Qualquer componente que precise desses dados disparará o fetch (componentes montados mantêm seus dados até que ele seja resolvido). Se nenhum componente se importar com esses dados, nenhuma ação será tomada.
Tempo de expiração
Endpoint.dataExpiryLength
Endpoint.dataExpiryLength define quanto tempo (em milissegundos) leva para os dados passarem do status 'fresh' para 'stale'. Experimente definir um número bem baixo, como '50', para que fiquem stale quase instantaneamente; ou um número muito grande para que permaneçam por muito tempo.
Alternar entre 'first' e 'second' muda os parâmetros. Se os dados ainda forem considerados fresh, você continuará vendo o horário antigo, sem nenhuma atualização.
import { lastUpdated } from './api/lastUpdated'; export const getUpdated = lastUpdated.extend({ dataExpiryLength: 10000 });
<script setup lang="ts"> import { useSuspense } from '@data-client/vue'; import { getUpdated } from './getUpdated'; const props = defineProps<{ id: string }>(); const time = await useSuspense(getUpdated, () => ({ id: props.id })); </script> <template> <div> API time for {{ id }}: <time>{{ time.updatedAt.toLocaleString('en-US', { timeStyle: 'long' }) }}</time> </div> </template>
@data-client/rest
Longa duração de cache
import {
RestEndpoint,
RestGenerics,
resource,
} from '@data-client/rest';
// We can now use LongLivingEndpoint to create endpoints that will be cached for one hour
class LongLivingEndpoint<
O extends RestGenerics,
> extends RestEndpoint<O> {
dataExpiryLength = 60 * 60 * 1000; // one hour
}
const LongLivingResource = resource({
path: '/:id',
Endpoint: LongLivingEndpoint,
});
Nunca tentar novamente em caso de erro
import {
RestEndpoint,
RestGenerics,
resource,
} from '@data-client/rest';
// We can now use NoRetryEndpoint to create endpoints that will be cached for one hour
class NoRetryEndpoint<
O extends RestGenerics,
> extends RestEndpoint<O> {
errorExpiryLength = Infinity;
}
const NoRetryResource = resource({
path: '/:id',
Endpoint: NoRetryEndpoint,
});
Endpoint.invalidIfStale
Endpoint.invalidIfStale elimina o status 'stale', fazendo com que dados que expiram sejam imediatamente considerados 'invalid'.
Isso é demonstrado pelo componente que suspende assim que seus dados ficam stale. Se os dados ainda estiverem dentro do tempo de expiração, ele simplesmente continua a exibi-los.
import { lastUpdated } from './api/lastUpdated'; export const getUpdated = lastUpdated.extend({ invalidIfStale: true, dataExpiryLength: 5000, });
<script setup lang="ts"> import { useSuspense } from '@data-client/vue'; import { getUpdated } from './getUpdated'; const props = defineProps<{ id: string }>(); const time = await useSuspense(getUpdated, () => ({ id: props.id })); </script> <template> <div> API time for {{ id }}: <time>{{ time.updatedAt.toLocaleString('en-US', { timeStyle: 'long' }) }}</time> </div> </template>
Forçar atualização
Às vezes queremos buscar dados novos, continuando a mostrar os dados antigos (stale).
Um endpoint específico
Controller.fetch pode ser usado para disparar um fetch enquanto ainda se mostram os dados anteriores. Isso pode ser feito mesmo com dados 'fresh'.
<script setup lang="ts"> import { useController, useSuspense } from '@data-client/vue'; import { lastUpdated } from './api/lastUpdated'; const time = await useSuspense(lastUpdated, { id: '1' }); const ctrl = useController(); </script> <template> <div> <time>{{ time.updatedAt.toLocaleString('en-US', { timeStyle: 'long' }) }}</time> <button @click="ctrl.fetch(lastUpdated, { id: '1' })">Refresh</button> </div> </template>
Atualizar endpoints visíveis
Controller.expireAll() define o status de expiração de todas as respostas que correspondem a testKey como Stale.
<script setup lang="ts"> import { useController } from '@data-client/vue'; import { lastUpdated } from './api/lastUpdated'; import ShowTime from './ShowTime.vue'; const ctrl = useController(); </script> <template> <div> <Suspense v-for="id in ['1', '2', '3']" :key="id"> <ShowTime :id="id" /> <template #fallback><div>{{ id }} Loading...</div></template> </Suspense> <button @click="ctrl.expireAll(lastUpdated)">Expire All</button> <button @click="ctrl.fetch(lastUpdated, { id: '1' })"> Force Refresh First </button> </div> </template>
Invalidate
Tanto endpoints quanto entities podem ser alvos de invalidação.
Dados invalidados sempre fazem refetch, mesmo quando estão fresh. O Vue não consegue suspender um componente novamente depois que seu
setup foi executado, então componentes montados continuam mostrando os dados anteriores até que o refetch seja resolvido.
Enquanto isso, useCache() retorna undefined e o loading de useDLE()
é true. Componentes montados após a invalidação suspendem até que os novos dados cheguem.
Um endpoint específico
Neste exemplo, invalidar o endpoint faz o refetch dele, mesmo que seus dados ainda estejam fresh.
<script setup lang="ts"> import { useController } from '@data-client/vue'; import { lastUpdated } from './api/lastUpdated'; import ShowTime from './ShowTime.vue'; const ctrl = useController(); </script> <template> <div> <Suspense v-for="id in ['1', '2', '3']" :key="id"> <ShowTime :id="id" /> <template #fallback><div>{{ id }} Loading...</div></template> </Suspense> <button @click="ctrl.invalidateAll(lastUpdated)">Invalidate All</button> <button @click="ctrl.invalidate(lastUpdated, { id: '1' })"> Invalidate First </button> </div> </template>
Qualquer endpoint com uma entity
Usar o schema Invalidate nos permite invalidar qualquer endpoint que dependa dessa entity em sua resposta. Se o endpoint usa a entity em um Array, ela simplesmente será removida desse Array.
<script setup lang="ts"> import { Invalidate, RestEndpoint } from '@data-client/rest'; import { useController, useLoading } from '@data-client/vue'; import { TimedEntity } from './api/lastUpdated'; import TimePage from './TimePage.vue'; const InvalidateTimedEntity = new Invalidate(TimedEntity); const deleteLastUpdated = new RestEndpoint({ path: '/api/currentTime/:id', method: 'DELETE', schema: InvalidateTimedEntity, }); const ctrl = useController(); const [handleDelete, loadingDelete] = useLoading(() => ctrl.fetch(deleteLastUpdated, { id: '1' }), ); </script> <template> <div> <Suspense> <TimePage id="1" /> <template #fallback><div>loading...</div></template> </Suspense> <button @click="handleDelete"> {{ loadingDelete ? 'loading...' : 'Invalidate' }} </button> <button @click="ctrl.setResponse(deleteLastUpdated, { id: '1' }, { id: '1' })" > Invalidate (without fetching DELETE) </button> <button @click="ctrl.set([InvalidateTimedEntity], [{ id: '1' }])"> Invalidate Entity with ctrl.set </button> </div> </template>
Controller.fetch() nos permite atualizar o servidor e o store. Podemos usar Controller.setResponse() ou Controller.set() quando queremos alterar diretamente o store local.
Invalidação condicional com base nos dados
Se a invalidation deve acontecer apenas às vezes, com base nos dados da resposta, podemos
retornar undefined de Entity.process.
class PriceLevel extends Entity {
price = 0;
amount = 0;
pk() {
return this.price;
}
static process(
input: [number, number],
parent: any,
key: string | undefined,
): any {
const [price, amount] = input;
if (amount === 0) return undefined;
return { price, amount };
}
}