Skip to content

Commit afce9dc

Browse files
ming4762mynetfan
andauthored
perf: improve destroyOnClose for VbenModal (vbenjs#5935)
* perf: 优化Vben Modal destroyOnClose,解决destroyOnClose=false,Modal依旧会被销毁的问题 影响范围(重要):destroyOnClose默认为true,这会导致所有的modal都会默认渲染到body radix-vue Dialog组件默认会销毁挂载的组件,所以即使destroyOnClose=false,Modal依旧会被销毁的问题 对于一些大表单重复渲染导致卡顿,ApiComponent也会频繁的加载数据 * fix: modal closing animation --------- Co-authored-by: Netfan <[email protected]>
1 parent b5700bd commit afce9dc

File tree

8 files changed

+39
-39
lines changed

8 files changed

+39
-39
lines changed

docs/src/components/common-ui/vben-modal.md

+1-2
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ Modal 内的内容一般业务中,会比较复杂,所以我们可以将 moda
6060

6161
- `VbenModal` 组件对与参数的处理优先级是 `slot` > `props` > `state`(通过api更新的状态以及useVbenModal参数)。如果你已经传入了 `slot` 或者 `props`,那么 `setState` 将不会生效,这种情况下你可以通过 `slot` 或者 `props` 来更新状态。
6262
- 如果你使用到了 `connectedComponent` 参数,那么会存在 2 个`useVbenModal`, 此时,如果同时设置了相同的参数,那么以内部为准(也就是没有设置 connectedComponent 的代码)。比如 同时设置了 `onConfirm`,那么以内部的 `onConfirm` 为准。`onOpenChange`事件除外,内外都会触发。
63-
- 使用了`connectedComponent`参数时,可以配置`destroyOnClose`属性来决定当关闭弹窗时,是否要销毁`connectedComponent`组件(重新创建`connectedComponent`组件,这将会把其内部所有的变量、状态、数据等恢复到初始状态。)。
6463
- 如果弹窗的默认行为不符合你的预期,可以在`src\bootstrap.ts`中修改`setDefaultModalProps`的参数来设置默认的属性,如默认隐藏全屏按钮,修改默认ZIndex等。
6564

6665
:::
@@ -84,7 +83,7 @@ const [Modal, modalApi] = useVbenModal({
8483
| --- | --- | --- | --- |
8584
| appendToMain | 是否挂载到内容区域(默认挂载到body) | `boolean` | `false` |
8685
| connectedComponent | 连接另一个Modal组件 | `Component` | - |
87-
| destroyOnClose | 关闭时销毁`connectedComponent` | `boolean` | `false` |
86+
| destroyOnClose | 关闭时销毁 | `boolean` | `false` |
8887
| title | 标题 | `string\|slot` | - |
8988
| titleTooltip | 标题提示信息 | `string\|slot` | - |
9089
| description | 描述信息 | `string\|slot` | - |

packages/@core/ui-kit/popup-ui/src/modal/modal-api.ts

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export class ModalApi {
4444
confirmDisabled: false,
4545
confirmLoading: false,
4646
contentClass: '',
47+
destroyOnClose: true,
4748
draggable: false,
4849
footer: true,
4950
footerClass: '',

packages/@core/ui-kit/popup-ui/src/modal/modal.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ export interface ModalProps {
6060
* 弹窗描述
6161
*/
6262
description?: string;
63+
/**
64+
* 在关闭时销毁弹窗
65+
*/
66+
destroyOnClose?: boolean;
6367
/**
6468
* 是否可拖拽
6569
* @default false
@@ -153,10 +157,6 @@ export interface ModalApiOptions extends ModalState {
153157
* 独立的弹窗组件
154158
*/
155159
connectedComponent?: Component;
156-
/**
157-
* 在关闭时销毁弹窗。仅在使用 connectedComponent 时有效
158-
*/
159-
destroyOnClose?: boolean;
160160
/**
161161
* 关闭前的回调,返回 false 可以阻止关闭
162162
* @returns

packages/@core/ui-kit/popup-ui/src/modal/modal.vue

+21-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script lang="ts" setup>
22
import type { ExtendedModalApi, ModalProps } from './modal';
33
4-
import { computed, nextTick, provide, ref, useId, watch } from 'vue';
4+
import { computed, nextTick, provide, ref, unref, useId, watch } from 'vue';
55
66
import {
77
useIsMobile,
@@ -34,6 +34,7 @@ interface Props extends ModalProps {
3434
3535
const props = withDefaults(defineProps<Props>(), {
3636
appendToMain: false,
37+
destroyOnClose: true,
3738
modalApi: undefined,
3839
});
3940
@@ -67,6 +68,7 @@ const {
6768
confirmText,
6869
contentClass,
6970
description,
71+
destroyOnClose,
7072
draggable,
7173
footer: showFooter,
7274
footerClass,
@@ -100,10 +102,15 @@ const { dragging, transform } = useModalDraggable(
100102
shouldDraggable,
101103
);
102104
105+
const firstOpened = ref(false);
106+
const isClosed = ref(false);
107+
103108
watch(
104109
() => state?.value?.isOpen,
105110
async (v) => {
106111
if (v) {
112+
isClosed.value = false;
113+
if (!firstOpened.value) firstOpened.value = true;
107114
await nextTick();
108115
if (!contentRef.value) return;
109116
const innerContentRef = contentRef.value.getContentRef();
@@ -113,6 +120,7 @@ watch(
113120
dialogRef.value.style.transform = `translate(${offsetX}px, ${offsetY}px)`;
114121
}
115122
},
123+
{ immediate: true },
116124
);
117125
118126
watch(
@@ -176,6 +184,15 @@ const getAppendTo = computed(() => {
176184
? `#${ELEMENT_ID_MAIN_CONTENT}>div:not(.absolute)>div`
177185
: undefined;
178186
});
187+
188+
const getForceMount = computed(() => {
189+
return !unref(destroyOnClose);
190+
});
191+
192+
function handleClosed() {
193+
isClosed.value = true;
194+
props.modalApi?.onClosed();
195+
}
179196
</script>
180197
<template>
181198
<Dialog
@@ -197,17 +214,19 @@ const getAppendTo = computed(() => {
197214
shouldFullscreen,
198215
'top-1/2 !-translate-y-1/2': centered && !shouldFullscreen,
199216
'duration-300': !dragging,
217+
hidden: isClosed,
200218
},
201219
)
202220
"
221+
:force-mount="getForceMount"
203222
:modal="modal"
204223
:open="state?.isOpen"
205224
:show-close="closable"
206225
:z-index="zIndex"
207226
:overlay-blur="overlayBlur"
208227
close-class="top-3"
209228
@close-auto-focus="handleFocusOutside"
210-
@closed="() => modalApi?.onClosed()"
229+
@closed="handleClosed"
211230
:close-disabled="submitting"
212231
@escape-key-down="escapeKeyDown"
213232
@focus-outside="handleFocusOutside"

packages/@core/ui-kit/popup-ui/src/modal/use-modal.ts

+2-24
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,6 @@
11
import type { ExtendedModalApi, ModalApiOptions, ModalProps } from './modal';
22

3-
import {
4-
defineComponent,
5-
h,
6-
inject,
7-
nextTick,
8-
provide,
9-
reactive,
10-
ref,
11-
} from 'vue';
3+
import { defineComponent, h, inject, nextTick, provide, reactive } from 'vue';
124

135
import { useStore } from '@vben-core/shared/store';
146

@@ -32,7 +24,6 @@ export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
3224
const { connectedComponent } = options;
3325
if (connectedComponent) {
3426
const extendedApi = reactive({});
35-
const isModalReady = ref(true);
3627
const Modal = defineComponent(
3728
(props: TParentModalProps, { attrs, slots }) => {
3829
provide(USER_MODAL_INJECT_KEY, {
@@ -42,11 +33,6 @@ export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
4233
Object.setPrototypeOf(extendedApi, api);
4334
},
4435
options,
45-
async reCreateModal() {
46-
isModalReady.value = false;
47-
await nextTick();
48-
isModalReady.value = true;
49-
},
5036
});
5137
checkProps(extendedApi as ExtendedModalApi, {
5238
...props,
@@ -55,7 +41,7 @@ export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
5541
});
5642
return () =>
5743
h(
58-
isModalReady.value ? connectedComponent : 'div',
44+
connectedComponent,
5945
{
6046
...props,
6147
...attrs,
@@ -84,14 +70,6 @@ export function useVbenModal<TParentModalProps extends ModalProps = ModalProps>(
8470
injectData.options?.onOpenChange?.(isOpen);
8571
};
8672

87-
const onClosed = mergedOptions.onClosed;
88-
89-
mergedOptions.onClosed = () => {
90-
onClosed?.();
91-
if (mergedOptions.destroyOnClose) {
92-
injectData.reCreateModal?.();
93-
}
94-
};
9573
const api = new ModalApi(mergedOptions);
9674

9775
const extendedApi: ExtendedModalApi = api as never;

playground/src/views/examples/modal/auto-height-demo.vue

+7-4
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,18 @@ const [Modal, modalApi] = useVbenModal({
1616
},
1717
onOpenChange(isOpen) {
1818
if (isOpen) {
19-
handleUpdate(10);
19+
handleUpdate();
2020
}
2121
},
2222
});
2323
24-
function handleUpdate(len: number) {
24+
function handleUpdate(len?: number) {
2525
modalApi.setState({ confirmDisabled: true, loading: true });
2626
setTimeout(() => {
27-
list.value = Array.from({ length: len }, (_v, k) => k + 1);
27+
list.value = Array.from(
28+
{ length: len ?? Math.floor(Math.random() * 10) + 1 },
29+
(_v, k) => k + 1,
30+
);
2831
modalApi.setState({ confirmDisabled: false, loading: false });
2932
}, 2000);
3033
}
@@ -40,7 +43,7 @@ function handleUpdate(len: number) {
4043
{{ item }}
4144
</div>
4245
<template #prepend-footer>
43-
<Button type="link" @click="handleUpdate(6)">点击更新数据</Button>
46+
<Button type="link" @click="handleUpdate()">点击更新数据</Button>
4447
</template>
4548
</Modal>
4649
</template>

playground/src/views/examples/modal/in-content-demo.vue

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const value = ref();
2424
title="基础弹窗示例"
2525
title-tooltip="标题提示内容"
2626
>
27-
此弹窗指定在内容区域打开
28-
<Input v-model="value" placeholder="KeepAlive测试" />
27+
此弹窗指定在内容区域打开,并且在关闭之后弹窗内容不会被销毁
28+
<Input v-model:value="value" placeholder="KeepAlive测试" />
2929
</Modal>
3030
</template>

playground/src/views/examples/modal/index.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ async function openPrompt() {
198198
</template>
199199
</Card>
200200

201-
<Card class="w-[300px]" title="指定容器">
201+
<Card class="w-[300px]" title="指定容器+关闭后不销毁">
202202
<p>在内容区域打开弹窗的示例</p>
203203
<template #actions>
204204
<Button type="primary" @click="openInContentModal">打开弹窗</Button>

0 commit comments

Comments
 (0)