Endpoint 过期策略
默认情况下,Reactive Data Client 的缓存策略可以描述为 stale-while-revalidate。这意味着当数据可用时,它可以使用过时的数据,从而避免阻塞应用。不过在后台,如果数据足够旧,它仍然会刷新数据。
过期状态
新鲜(Fresh)
处于这种状态的数据被认为足够新,无需获取。
过时(Stale)
数据仍然可以展示,但 Reactive Data Client 可能会尝试重新获取以重新验证。
useSuspense() 会在挂载时以及参数变化时考虑是否获取。在这些情况下,如果数据被视为过时,它就会发起获取。
使用 React Navigation 时,焦点事件也会为过时的数据触发获取。
无效(Invalid)
数据不应展示。任何需要这些数据的组件都会触发获取并挂起。如果没有组件关心这些数据,则不会执行任何操作。
过期时间
Endpoint.dataExpiryLength
Endpoint.dataExpiryLength 用于设置数据从“新鲜”状态转变为“过时”状态所需的时间(毫秒)。试着把它设置为很小的数字,比如 '50',让数据几乎立即变为过时;或者设置为很大的数字,让数据保留很长时间。
在 'first' 和 'second' 之间切换会改变参数。如果数据仍被视为新鲜,你会一直看到旧的时间,而不会发生任何刷新。
import { lastUpdated } from './api/lastUpdated'; export const getUpdated = lastUpdated.extend({ dataExpiryLength: 10000 });
import { useSuspense } from '@data-client/react'; import { getUpdated } from './getUpdated'; export default function TimePage({ id }) { const { updatedAt } = useSuspense(getUpdated, { id }); return ( <div> API time for {id}:{' '} <time> {updatedAt.toLocaleString('en-US', { timeStyle: 'long' })} </time> </div> ); }
@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, });
import { useSuspense } from '@data-client/react'; import { getUpdated } from './getUpdated'; export default function TimePage({ id }) { const { updatedAt } = useSuspense(getUpdated, { id }); return ( <div> API time for {id}:{' '} <time> {updatedAt.toLocaleString('en-US', { timeStyle: 'long' })} </time> </div> ); }
强制刷新
有时我们想要获取新数据,同时继续展示旧的(过时的)数据。
某个特定的 endpoint
Controller.fetch 可以用来触发获取,同时仍展示之前的数据。即使数据是“新鲜”的,也可以这样做。
import { useSuspense, useController } from '@data-client/react'; import { lastUpdated } from './api/lastUpdated'; function ShowTime() { const { updatedAt } = useSuspense(lastUpdated, { id: '1' }); const ctrl = useController(); return ( <div> <time> {updatedAt.toLocaleString('en-US', { timeStyle: 'long' })} </time>{' '} <button onClick={() => ctrl.fetch(lastUpdated, { id: '1' })}> Refresh </button> </div> ); } render(<ShowTime />);
刷新可见的 endpoint
Controller.expireAll() 会把所有与 testKey 匹配的响应的过期状态设置为过时。
import { AsyncBoundary, useController } from '@data-client/react'; import { lastUpdated } from './api/lastUpdated'; import ShowTime from './ShowTime'; import Loading from './Loading'; function Demo() { const ctrl = useController(); return ( <div> <AsyncBoundary fallback={<Loading id="1" />}> <ShowTime id="1" /> </AsyncBoundary> <AsyncBoundary fallback={<Loading id="2" />}> <ShowTime id="2" /> </AsyncBoundary> <AsyncBoundary fallback={<Loading id="3" />}> <ShowTime id="3" /> </AsyncBoundary> <button onClick={() => ctrl.expireAll(lastUpdated)}> Expire All </button> <button onClick={() => ctrl.fetch(lastUpdated, { id: '1' })}> Force Refresh First </button> </div> ); } render(<Demo />);
使失效(重新挂起)
某个特定的 endpoint
在这个示例中,使 endpoint 失效会显示加载 fallback,因为此时数据不允许展示。
import { AsyncBoundary, useController } from '@data-client/react'; import { lastUpdated } from './api/lastUpdated'; import ShowTime from './ShowTime'; import Loading from './Loading'; function Demo() { const ctrl = useController(); return ( <div> <AsyncBoundary fallback={<Loading id="1" />}> <ShowTime id="1" /> </AsyncBoundary> <AsyncBoundary fallback={<Loading id="2" />}> <ShowTime id="2" /> </AsyncBoundary> <AsyncBoundary fallback={<Loading id="3" />}> <ShowTime id="3" /> </AsyncBoundary> <button onClick={() => ctrl.invalidateAll(lastUpdated)}> Invalidate All </button> <button onClick={() => ctrl.invalidate(lastUpdated, { id: '1' })} > Invalidate First </button> </div> ); } render(<Demo />);
包含某个 Entity 的任意 endpoint
使用 Invalidate schema,我们可以让响应中依赖该 Entity 的_任意_ endpoint 失效。如果 endpoint 在 Array 中使用了该 Entity,它只会被从该 Array 中移除。
import { Invalidate, RestEndpoint } from '@data-client/rest'; import { AsyncBoundary, useController, useLoading } from '@data-client/react'; import { TimedEntity } from './api/lastUpdated'; import TimePage from './TimePage'; const InvalidateTimedEntity = new Invalidate(TimedEntity); export const deleteLastUpdated = new RestEndpoint({ path: '/api/currentTime/:id', method: 'DELETE', schema: InvalidateTimedEntity, }); function ShowTime() { const ctrl = useController(); const [handleDelete, loadingDelete] = useLoading( () => ctrl.fetch(deleteLastUpdated, { id: '1' }), [], ); return ( <div> <AsyncBoundary fallback={<div>loading...</div>}> <TimePage id="1" /> </AsyncBoundary> <div> Current Time: <CurrentTime /> </div> <button onClick={handleDelete}> {loadingDelete ? 'loading...' : 'Invalidate'} </button> <button onClick={() => ctrl.setResponse( deleteLastUpdated, { id: '1' }, { id: '1' }, ) } > Invalidate (without fetching DELETE) </button> <button onClick={() => ctrl.set([InvalidateTimedEntity], [{ id: '1' }])} > Invalidate Entity with ctrl.set </button> </div> ); } render(<ShowTime />);
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 };
}
}