From d0c8c887fd81df716c9f76c18a000d49264982d3 Mon Sep 17 00:00:00 2001
From: Dream <1012377328@qq.com>
Date: Wed, 5 Aug 2026 09:54:01 +0800
Subject: [PATCH] docs: document typed popup data contracts
---
docs/src/components/common-ui/vben-drawer.md | 27 +++++++++++++++++--
docs/src/components/common-ui/vben-modal.md | 27 +++++++++++++++++--
.../demos/vben-drawer/shared-data/drawer.vue | 13 ++++++---
.../demos/vben-modal/shared-data/modal.vue | 13 ++++++---
.../en/components/common-ui/vben-drawer.md | 27 +++++++++++++++++--
.../src/en/components/common-ui/vben-modal.md | 27 +++++++++++++++++--
6 files changed, 120 insertions(+), 14 deletions(-)
diff --git a/docs/src/components/common-ui/vben-drawer.md b/docs/src/components/common-ui/vben-drawer.md
index 8214b855..8f255958 100644
--- a/docs/src/components/common-ui/vben-drawer.md
+++ b/docs/src/components/common-ui/vben-drawer.md
@@ -50,6 +50,29 @@ Drawer 内的内容一般业务中,会比较复杂,所以我们可以将 dra
+### 数据类型约束
+
+推荐在 connected 子组件中声明一次数据类型并暴露 `drawerApi`,外部会从 `connectedComponent` 自动推导 `setData` 和 `getData` 的类型:
+
+```ts
+// connected 子组件
+const [Drawer, drawerApi] = useVbenDrawer();
+defineExpose({ drawerApi });
+
+// 外部组件,无需重复声明 EditData
+const [Drawer, drawerApi] = useVbenDrawer({
+ connectedComponent: EditDrawer,
+});
+```
+
+无法从组件公开实例推导时,可以显式使用 `useVbenDrawer()`。需要让多个文件共享同一契约时,可以在独立模块中预绑定:
+
+```ts
+export const useEditDrawer = createVbenDrawer();
+```
+
+三种方式的优先级为:显式泛型、connected component 自动推导、`unknown`。普通 SFC 通过 `defineExpose` 支持自动推导;泛型 SFC、函数式组件或被标注为宽 `Component` 的组件应使用显式泛型或契约工厂。`getData()` 在尚未调用 `setData()` 时返回 `undefined`,业务允许 `null`、部分对象等值时,需要在数据泛型中准确声明。
+
::: info 注意
- `VbenDrawer` 组件对于参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenDrawer参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。
@@ -143,8 +166,8 @@ const [Drawer, drawerApi] = useVbenDrawer({
| setState | 动态设置抽屉状态属性 | `(((prev: DrawerState) => Partial)\| Partial)=>drawerApi` |
| open | 打开弹窗 | `()=>void` | --- |
| close | 关闭弹窗 | `()=>void` | --- |
-| setData | 设置共享数据 | `(data:T)=>drawerApi` | --- |
-| getData | 获取共享数据 | `()=>T` | --- |
+| setData | 设置共享数据 | `(data:TData)=>drawerApi` | --- |
+| getData | 获取共享数据 | `()=>TData\|undefined` | --- |
| useStore | 获取可响应式状态 | - | --- |
| lock | 将抽屉标记为提交中,锁定当前状态 | `(isLock:boolean)=>drawerApi` | >5.5.3 |
| unlock | lock方法的反操作,解除抽屉的锁定状态,也是lock(false)的别名 | `()=>drawerApi` | >5.5.3 |
diff --git a/docs/src/components/common-ui/vben-modal.md b/docs/src/components/common-ui/vben-modal.md
index 65c2912d..29d7fee7 100644
--- a/docs/src/components/common-ui/vben-modal.md
+++ b/docs/src/components/common-ui/vben-modal.md
@@ -56,6 +56,29 @@ Modal 内的内容一般业务中,会比较复杂,所以我们可以将 moda
+### 数据类型约束
+
+推荐在 connected 子组件中声明一次数据类型并暴露 `modalApi`,外部会从 `connectedComponent` 自动推导 `setData` 和 `getData` 的类型:
+
+```ts
+// connected 子组件
+const [Modal, modalApi] = useVbenModal();
+defineExpose({ modalApi });
+
+// 外部组件,无需重复声明 EditData
+const [Modal, modalApi] = useVbenModal({
+ connectedComponent: EditModal,
+});
+```
+
+无法从组件公开实例推导时,可以显式使用 `useVbenModal()`。需要让多个文件共享同一契约时,可以在独立模块中预绑定:
+
+```ts
+export const useEditModal = createVbenModal();
+```
+
+三种方式的优先级为:显式泛型、connected component 自动推导、`unknown`。普通 SFC 通过 `defineExpose` 支持自动推导;泛型 SFC、函数式组件或被标注为宽 `Component` 的组件应使用显式泛型或契约工厂。`getData()` 在尚未调用 `setData()` 时返回 `undefined`,业务允许 `null`、部分对象等值时,需要在数据泛型中准确声明。
+
## 动画类型
通过 `animationType` 属性可以控制弹窗的动画效果:
@@ -162,8 +185,8 @@ const [Modal, modalApi] = useVbenModal({
| setState | 动态设置弹窗状态属性 | `(((prev: ModalState) => Partial)\| Partial)=>modalApi` | - |
| open | 打开弹窗 | `()=>void` | - |
| close | 关闭弹窗 | `()=>void` | - |
-| setData | 设置共享数据 | `(data:T)=>modalApi` | - |
-| getData | 获取共享数据 | `()=>T` | - |
+| setData | 设置共享数据 | `(data:TData)=>modalApi` | - |
+| getData | 获取共享数据 | `()=>TData\|undefined` | - |
| useStore | 获取可响应式状态 | - | - |
| lock | 将弹窗标记为提交中,锁定当前状态 | `(isLock:boolean)=>modalApi` | >5.5.2 |
| unlock | lock方法的反操作,解除弹窗的锁定状态,也是lock(false)的别名 | `()=>modalApi` | >5.5.3 |
diff --git a/docs/src/demos/vben-drawer/shared-data/drawer.vue b/docs/src/demos/vben-drawer/shared-data/drawer.vue
index 629199b6..0cd9707b 100644
--- a/docs/src/demos/vben-drawer/shared-data/drawer.vue
+++ b/docs/src/demos/vben-drawer/shared-data/drawer.vue
@@ -3,9 +3,14 @@ import { ref } from 'vue';
import { useVbenDrawer } from '@vben/common-ui';
-const data = ref();
+interface SharedData {
+ content: string;
+ payload: string;
+}
-const [Drawer, drawerApi] = useVbenDrawer({
+const data = ref();
+
+const [Drawer, drawerApi] = useVbenDrawer({
onCancel() {
drawerApi.close();
},
@@ -14,10 +19,12 @@ const [Drawer, drawerApi] = useVbenDrawer({
},
onOpenChange(isOpen: boolean) {
if (isOpen) {
- data.value = drawerApi.getData>();
+ data.value = drawerApi.getData();
}
},
});
+
+defineExpose({ drawerApi });
diff --git a/docs/src/demos/vben-modal/shared-data/modal.vue b/docs/src/demos/vben-modal/shared-data/modal.vue
index 806585d9..5ed68c09 100644
--- a/docs/src/demos/vben-modal/shared-data/modal.vue
+++ b/docs/src/demos/vben-modal/shared-data/modal.vue
@@ -3,9 +3,14 @@ import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
-const data = ref();
+interface SharedData {
+ content: string;
+ payload: string;
+}
-const [Modal, modalApi] = useVbenModal({
+const data = ref();
+
+const [Modal, modalApi] = useVbenModal({
onCancel() {
modalApi.close();
},
@@ -14,10 +19,12 @@ const [Modal, modalApi] = useVbenModal({
},
onOpenChange(isOpen: boolean) {
if (isOpen) {
- data.value = modalApi.getData>();
+ data.value = modalApi.getData();
}
},
});
+
+defineExpose({ modalApi });
diff --git a/docs/src/en/components/common-ui/vben-drawer.md b/docs/src/en/components/common-ui/vben-drawer.md
index 20b1d7dd..83448b29 100644
--- a/docs/src/en/components/common-ui/vben-drawer.md
+++ b/docs/src/en/components/common-ui/vben-drawer.md
@@ -23,6 +23,29 @@ const [Drawer, drawerApi] = useVbenDrawer({
- Default drawer behavior can be adjusted in `apps//src/bootstrap.ts` through `setDefaultDrawerProps(...)`.
- `setState(...)` works on `DrawerState`, not `ModalState`.
+## Shared Data Types
+
+The recommended approach is to declare the data type once in the connected component and expose `drawerApi`. The outer call then infers the data contract from `connectedComponent`:
+
+```ts
+// Connected component
+const [Drawer, drawerApi] = useVbenDrawer();
+defineExpose({ drawerApi });
+
+// Outer component, EditData is inferred
+const [Drawer, drawerApi] = useVbenDrawer({
+ connectedComponent: EditDrawer,
+});
+```
+
+Use `useVbenDrawer()` explicitly when the component type cannot expose the contract. For larger features, pre-bind one reusable contract in a separate module:
+
+```ts
+export const useEditDrawer = createVbenDrawer();
+```
+
+The precedence is explicit generic, connected component inference, then `unknown`. Plain SFCs support inference through `defineExpose`; generic SFCs, functional components, and components widened to `Component` should use an explicit generic or contract factory. `getData()` returns `undefined` before `setData()` is called. Include `null` or partial payloads in the data type when they are valid business values.
+
## Key Props
| Prop | Description | Type |
@@ -50,7 +73,7 @@ const [Drawer, drawerApi] = useVbenDrawer({
| `setState(...)` | updates drawer state |
| `open()` | opens the drawer |
| `close()` | closes the drawer |
-| `setData(data)` | stores shared data |
-| `getData()` | reads shared data |
+| `setData(data: TData)` | stores typed shared data |
+| `getData()` | returns `TData \| undefined` |
| `lock(isLocked = true)` | locks the drawer into submitting state |
| `unlock()` | alias for `lock(false)` |
diff --git a/docs/src/en/components/common-ui/vben-modal.md b/docs/src/en/components/common-ui/vben-modal.md
index 0971c441..76c2b088 100644
--- a/docs/src/en/components/common-ui/vben-modal.md
+++ b/docs/src/en/components/common-ui/vben-modal.md
@@ -23,6 +23,29 @@ const [Modal, modalApi] = useVbenModal({
- When `connectedComponent` is present, avoid pushing extra modal props through the connected side. Prefer `useVbenModal(...)` or `modalApi.setState(...)`.
- Default modal behavior can be adjusted in `apps//src/bootstrap.ts` through `setDefaultModalProps(...)`.
+## Shared Data Types
+
+The recommended approach is to declare the data type once in the connected component and expose `modalApi`. The outer call then infers the data contract from `connectedComponent`:
+
+```ts
+// Connected component
+const [Modal, modalApi] = useVbenModal();
+defineExpose({ modalApi });
+
+// Outer component, EditData is inferred
+const [Modal, modalApi] = useVbenModal({
+ connectedComponent: EditModal,
+});
+```
+
+Use `useVbenModal()` explicitly when the component type cannot expose the contract. For larger features, pre-bind one reusable contract in a separate module:
+
+```ts
+export const useEditModal = createVbenModal();
+```
+
+The precedence is explicit generic, connected component inference, then `unknown`. Plain SFCs support inference through `defineExpose`; generic SFCs, functional components, and components widened to `Component` should use an explicit generic or contract factory. `getData()` returns `undefined` before `setData()` is called. Include `null` or partial payloads in the data type when they are valid business values.
+
## Key Props
| Prop | Description | Type |
@@ -50,7 +73,7 @@ const [Modal, modalApi] = useVbenModal({
| `setState(...)` | updates modal state |
| `open()` | opens the modal |
| `close()` | closes the modal |
-| `setData(data)` | stores shared data |
-| `getData()` | reads shared data |
+| `setData(data: TData)` | stores typed shared data |
+| `getData()` | returns `TData \| undefined` |
| `lock(isLocked = true)` | locks the modal into submitting state |
| `unlock()` | alias for `lock(false)` |