DataClientPlugin
一个 Vue 插件,负责创建 store 和
Controller,并将它们提供给应用中的每个组件。请在 app.mount() 之前安装一次;composable 只能在安装了它的应用的组件中使用。
import { createApp } from 'vue';
import { DataClientPlugin } from '@data-client/vue';
import App from './App.vue';
const app = createApp(App);
app.use(DataClientPlugin);
app.mount('#app');
Manager 会在插件安装时启动,并在应用卸载时停止。
选项
app.use(DataClientPlugin, options);
interface ProvideOptions {
managers?: Manager[];
initialState?: State<unknown>;
Controller?: new (props: { gcPolicy: GCInterface }) => Controller;
gcPolicy?: GCInterface;
}
managers?: Manager[]
要使用的 Manager 列表。这是 store 的主要扩展点。
默认为 getDefaultManagers(),它也可以用来扩展默认值。
import { createApp } from 'vue';
import { DataClientPlugin, getDefaultManagers } from '@data-client/vue';
import App from './App.vue';
import MyManager from './MyManager';
const app = createApp(App);
app.use(DataClientPlugin, {
managers: [...getDefaultManagers(), new MyManager()],
});
生产环境默认值:
[new NetworkManager(), new SubscriptionManager(PollingSubscription)];
开发环境默认值:
[
new DevToolsManager(),
new NetworkManager(),
new SubscriptionManager(PollingSubscription),
];
initialState?: State<unknown>
你可以提供自己的初始状态,而不是从空缓存开始。这在测试时很有用,也可用于在服务端渲染时恢复(rehydrate)缓存状态。 mockInitialState() 可以基于 fixture 构建初始状态。
app.use(DataClientPlugin, { initialState: window.__INITIAL_STATE__ });
export interface State<T> {
readonly entities: {
readonly [entityKey: string]: { readonly [pk: string]: T } | undefined;
};
readonly endpoints: {
readonly [key: string]: unknown | PK[] | PK | undefined;
};
readonly indexes: NormalizedIndex;
readonly meta: {
readonly [key: string]: {
readonly date: number;
readonly fetchedAt: number;
readonly expiresAt: number;
readonly prevExpiresAt?: number;
readonly error?: ErrorTypes;
readonly invalidated?: boolean;
readonly errorPolicy?: 'hard' | 'soft' | undefined;
};
};
readonly entitiesMeta: {
readonly [entityKey: string]: {
readonly [pk: string]: {
readonly date: number;
readonly expiresAt: number;
readonly fetchedAt: number;
};
};
};
readonly optimistic: (SetResponseAction | OptimisticAction)[];
readonly lastReset: number;
}
Controller?: Controller 类
这让你可以扩展 Controller 来提供额外的功能。如果你有额外的 action 想要 dispatch 给自定义 Manager,这会很有用。
import { createApp } from 'vue';
import { Controller, DataClientPlugin } from '@data-client/vue';
import App from './App.vue';
export class MyController extends Controller {
doSomething = () => {
console.log('hi');
};
}
const app = createApp(App);
app.use(DataClientPlugin, { Controller: MyController });
之后 useController() 和 $dataClient 会返回 MyController 实例,但它们的类型仍然是 Controller。需要进行类型断言才能访问新增的成员:
import { useController } from '@data-client/vue';
import type { MyController } from './main';
const ctrl = useController() as MyController;
ctrl.doSomething();
gcPolicy?: GCInterface
当数据不再被任何组件使用且已经过时后,将其从 store 中移除。默认为
new GCPolicy();传入一个实例可以更改清理频率或未使用数据的保留时长。
import { createApp } from 'vue';
import { DataClientPlugin, GCPolicy } from '@data-client/vue';
import App from './App.vue';
const app = createApp(App);
app.use(DataClientPlugin, {
// sweep every 10 minutes
gcPolicy: new GCPolicy({ intervalMS: 60 * 1000 * 10 }),
});
new GCPolicy({
// how often to sweep (default 5 minutes)
intervalMS: 60 * 1000 * 5,
// how many stale lifetimes before data is removed (default 2)
expiryMultiplier: 2,
// or choose when unused data is removed (replaces expiryMultiplier)
// here: one minute after it goes stale
expiresAt: ({ expiresAt }) => expiresAt + 60 * 1000,
});
$dataClient
该插件还会将 Controller 添加为全局属性 $dataClient,因此模板和 Options API 组件(通过 this.$dataClient)无需
useController() 即可使用它。它的类型为 Controller,无需额外配置。
<script setup lang="ts">
import { TodoResource } from '@/resources/Todo';
defineProps<{ id: number }>();
</script>
<template>
<button @click="$dataClient.fetch(TodoResource.delete, { id })">
Delete
</button>
</template>
使用 composable
像 useSuspense() 这样的 composable 必须在组件的 setup 期间运行,这样 Vue
才知道该使用哪个应用的 store。要 await 它们需要使用 <script setup>:在手写的
async setup() 中,在第一个 await 之后调用的 composable 会丢失组件实例并抛出错误。
<script setup lang="ts">
import { useSuspense } from '@data-client/vue';
import { TodoResource } from '@/resources/Todo';
import { UserResource } from '@/resources/User';
const todo = await useSuspense(TodoResource.get, { id: 1 });
// still works after the await
const user = await useSuspense(UserResource.get, {
id: todo.value.userId,
});
</script>
使用 await 的组件必须在 <Suspense>
边界内渲染。