Explorar o código

chore: add demo for apiComponent with caching and concurrency (#5827)

* chore: add demo for apiComponent with caching and concurrency

* docs: update api component docs
Netfan hai 5 meses
pai
achega
166e9a0e82

+ 11 - 0
docs/src/components/common-ui/vben-api-component.md

@@ -123,6 +123,10 @@ function fetchApi(): Promise<Record<string, any>> {
 
 :::
 
+## 并发和缓存
+
+有些场景下可能需要使用多个ApiComponent,它们使用了相同的远程数据源(例如用在可编辑的表格中)。如果直接将请求后端接口的函数传递给api属性,则每一个实例都会访问一次接口,这会造成资源浪费,是完全没有必要的。Tanstack Query提供了并发控制、缓存、重试等诸多特性,我们可以将接口请求函数用useQuery包装一下再传递给ApiComponent,这样的话无论页面有多少个使用相同数据源的ApiComponent实例,都只会发起一次远程请求。演示效果请参考 [Playground vue-query](http://localhost:5555/demos/features/vue-query),具体代码请查看项目文件[concurrency-caching](https://github.com/vbenjs/vue-vben-admin/blob/main/playground/src/views/demos/features/vue-query/concurrency-caching.vue)
+
 ## API
 
 ### Props
@@ -147,3 +151,10 @@ function fetchApi(): Promise<Record<string, any>> {
 | options | 直接传入选项数据,也作为api返回空数据时的后备数据 | `OptionsItem[]` | - |
 | visibleEvent | 触发重新请求数据的事件名 | `string` | - |
 | loadingSlot | 目标组件的插槽名称,用来显示一个"加载中"的图标 | `string` | - |
+
+### Methods
+
+| 方法 | 描述 | 类型 | 版本要求 |
+| --- | --- | --- | --- |
+| getComponentRef | 获取被包装的组件的实例 | ()=>T | >5.5.4 |
+| updateParam | 设置接口请求参数(将与params属性合并) | (newParams: Record<string, any>)=>void | >5.5.4 |

+ 61 - 0
playground/src/views/demos/features/vue-query/concurrency-caching.vue

@@ -0,0 +1,61 @@
+<script lang="ts" setup>
+import type { Recordable } from '@vben/types';
+
+import { useQuery } from '@tanstack/vue-query';
+
+import { useVbenForm } from '#/adapter/form';
+import { getMenuList } from '#/api';
+
+const queryKey = ['demo', 'api', 'options'];
+const count = 4;
+
+const { dataUpdatedAt, promise: fetchDataFn } = useQuery({
+  // 在组件渲染期间预取数据
+  experimental_prefetchInRender: true,
+  // 获取接口数据的函数
+  queryFn: getMenuList,
+  queryKey,
+  // 每次组件挂载时都重新获取数据。如果不需要每次都重新获取就不要设置为always
+  refetchOnMount: 'always',
+  // 缓存时间
+  staleTime: 1000 * 60 * 5,
+});
+
+async function fetchOptions() {
+  return await fetchDataFn.value;
+}
+
+const schema = [];
+
+for (let i = 0; i < count; i++) {
+  schema.push({
+    component: 'ApiSelect',
+    componentProps: {
+      api: fetchOptions,
+      class: 'w-full',
+      filterOption: (input: string, option: Recordable<any>) => {
+        return option.label.toLowerCase().includes(input.toLowerCase());
+      },
+      labelField: 'name',
+      showSearch: true,
+      valueField: 'id',
+    },
+    fieldName: `field${i}`,
+    label: `Select ${i}`,
+  });
+}
+
+const [Form] = useVbenForm({
+  schema,
+  showDefaultActions: false,
+});
+</script>
+<template>
+  <div>
+    <div class="mb-2 flex gap-2">
+      <div>以下{{ count }}个组件共用一个数据源。</div>
+      <div>缓存更新时间:{{ new Date(dataUpdatedAt).toLocaleString() }}</div>
+    </div>
+    <Form />
+  </div>
+</template>

+ 16 - 1
playground/src/views/demos/features/vue-query/index.vue

@@ -1,11 +1,15 @@
 <script setup lang="ts">
 import { Page } from '@vben/common-ui';
 
-import { Card } from 'ant-design-vue';
+import { refAutoReset } from '@vueuse/core';
+import { Button, Card, Empty } from 'ant-design-vue';
 
+import ConcurrencyCaching from './concurrency-caching.vue';
 import InfiniteQueries from './infinite-queries.vue';
 import PaginatedQueries from './paginated-queries.vue';
 import QueryRetries from './query-retries.vue';
+
+const showCaching = refAutoReset(true, 1000);
 </script>
 
 <template>
@@ -20,6 +24,17 @@ import QueryRetries from './query-retries.vue';
       <Card title="错误重试">
         <QueryRetries />
       </Card>
+      <Card
+        title="并发和缓存"
+        v-spinning="!showCaching"
+        :body-style="{ minHeight: '330px' }"
+      >
+        <template #extra>
+          <Button @click="showCaching = false">重新加载</Button>
+        </template>
+        <ConcurrencyCaching v-if="showCaching" />
+        <Empty v-else description="正在加载..." />
+      </Card>
     </div>
   </Page>
 </template>