Pular para o conteúdo principal

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

LongLivingResource.ts
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

NoRetryResource.ts
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 };
}
}