Política de caducidad de Endpoint
Por defecto, la política de caché de Reactive Data Client se puede describir como stale-while-revalidate. Esto significa que, cuando hay datos disponibles, puede evitar bloquear la aplicación usando los datos obsoletos. Sin embargo, en segundo plano seguirá actualizando los datos si son lo bastante antiguos.
Estado de caducidad
Fresh
Los datos en este estado se consideran lo bastante recientes como para no necesitar un fetch.
Stale
Los datos todavía se pueden mostrar, pero Reactive Data Client podría intentar revalidarlos haciendo un nuevo fetch.
useSuspense() considera hacer fetch al montarse y también cuando cambian sus parámetros. En estos casos hará fetch si los datos se consideran obsoletos.
Invalid
Los datos no deben mostrarse. Cualquier componente que necesite estos datos desencadenará un fetch (los componentes montados conservan sus datos hasta que se resuelva). Si ningún componente necesita estos datos, no se realizará ninguna acción.
Tiempo de caducidad
Endpoint.dataExpiryLength
Endpoint.dataExpiryLength establece cuánto tiempo (en milisegundos) tardan los datos en pasar del estado 'fresh' al estado 'stale'. Prueba a establecerlo en un número muy bajo, como '50', para que pasen a stale casi al instante; o en un número muy grande para que se mantengan durante mucho tiempo.
Alternar entre 'first' y 'second' cambia los parámetros. Si los datos todavía se consideran fresh, seguirás viendo la hora anterior sin ninguna actualización.
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
Tiempo de vida largo en la caché
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 reintentar en caso de error
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 el estado 'stale', de modo que los datos que caducan se consideran 'invalid' de inmediato.
Esto se demuestra porque el componente se suspende en cuanto sus datos pasan a stale. Si los datos aún están dentro del tiempo de caducidad, simplemente sigue mostrándolos.
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>
Forzar la actualización
A veces queremos obtener datos nuevos mientras seguimos mostrando los datos antiguos (obsoletos).
Un endpoint específico
Controller.fetch se puede usar para desencadenar un fetch mientras se siguen mostrando los datos anteriores. Esto se puede hacer incluso con datos '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>
Actualizar los endpoints visibles
Controller.expireAll() establece en Stale el estado de caducidad de todas las respuestas que coincidan con testKey.
<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>
Invalidar
Tanto los endpoints como las entities pueden invalidarse.
Los datos invalidados siempre se vuelven a obtener, incluso cuando están fresh. Vue no puede volver a suspender un componente una vez que su
setup se ha ejecutado, así que los componentes montados siguen mostrando sus datos anteriores hasta que se resuelva el nuevo fetch.
Mientras tanto, useCache() devuelve undefined y el loading de useDLE()
es true. Los componentes montados después de la invalidación se suspenden hasta que llegan los datos nuevos.
Un endpoint específico
En este ejemplo, invalidar el endpoint lo vuelve a obtener, aunque sus datos todavía estén 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>
Cualquier endpoint con una entity
Usar el schema Invalidate nos permite invalidar cualquier endpoint que dependa de esa entity en su respuesta. Si el endpoint usa la entity dentro de un Array, simplemente se eliminará de ese 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 actualizar el servidor y el store. Podemos usar Controller.setResponse() o Controller.set() cuando queremos modificar directamente el store local.
Invalidación condicional según los datos
Si la invalidation debe ocurrir solo a veces, según los datos de la respuesta, podemos
devolver undefined desde 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 };
}
}