Endpoint 过期策略
默认情况下,Reactive Data Client 的缓存策略可以描述为 stale-while-revalidate。这意味着当数据可用时,它可以使用过时的数据,从而避免阻塞应用。不过在后台,如果数据足够旧,它仍然会刷新数据。
过期状态
新鲜(Fresh)
处于这种状态的数据被认为足够新,无需获取。
过时(Stale)
数据仍然可以展示,但 Reactive Data Client 可能会尝试重新获取以重新验证。
useSuspense() 会在挂载时以及参数变化时考虑是否获取。在这些情况下,如果数据被视为过时,它就会发起获取。
无效(Invalid)
数据不应展示。任何需要这些数据的组件都会触发获取(在获取完成之前,已挂载的组件会保留其数据)。如果没有组件关心这些数据,则不会执行任何操作。
过期时间
Endpoint.dataExpiryLength
Endpoint.dataExpiryLength 用于设置数据从“新鲜”状态转变为“过时”状态所需的时间(毫秒)。试着把它设置为很小的数字,比如 '50',让数据几乎立即变为过时;或者设置为很大的数字,让数据保留很长时间。
在 'first' 和 'second' 之间切换会改变参数。如果数据仍被视为新鲜,你会一直看到旧的时间,而不会发生任何刷新。
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
较长的缓存寿命
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,
});
出错时从不重试
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 去掉了“过时”状态,使得过期的数据立即被视为“无效”。
在演示中,组件的数据一旦过时,组件就会挂起。如果数据仍在过期时间之内,组件就会继续展示它。
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>
强制刷新
有时我们想要获取新数据,同时继续展示旧的(过时的)数据。
某个特定的 endpoint
Controller.fetch 可以用来触发获取,同时仍展示之前的数据。即使数据是“新鲜”的,也可以这样做。
<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>
刷新可见的 endpoint
Controller.expireAll() 会把所有与 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>
使失效
失效的数据总是会重新获取,即使它仍然新鲜。组件的 setup 一旦运行,Vue 就无法再次挂起该组件,因此已挂载的组件会继续展示之前的数据,直到重新获取完成。在此期间,useCache() 返回 undefined,useDLE() 的 loading
为 true。在失效之后挂载的组件会挂起,直到新数据到达。
某个特定的 endpoint
在这个示例中,使 endpoint 失效会重新获取它,即使其数据仍然新鲜。
<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>
包含某个 Entity 的任意 endpoint
使用 Invalidate schema,我们可以让响应中依赖该 Entity 的_任意_ endpoint 失效。如果 endpoint 在 Array 中使用了该 Entity,它只会被从该 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() 让我们可以同时更新服务器和 store。当我们想直接修改本地 store 时,可以使用 Controller.setResponse() 或 Controller.set()。
根据数据有条件地失效
如果 invalidation 只应在某些情况下根据响应数据发生,我们可以从 Entity.process 返回 undefined。
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 };
}
}