perf: useModal #6517

pull/6527/head
tangjinzhou 2023-05-03 15:09:01 +08:00
parent 69640c0af8
commit 5578c14a8e
6 changed files with 71 additions and 67 deletions

View File

@ -31,8 +31,8 @@ module.exports = {
testRegex: getTestRegex(libDir), testRegex: getTestRegex(libDir),
moduleNameMapper: { moduleNameMapper: {
'^@/(.*)$/': '<rootDir>/$1', '^@/(.*)$/': '<rootDir>/$1',
'ant-design-vue$/': '<rootDir>/components/index.ts', '^ant-design-vue$': '<rootDir>/components/index',
'ant-design-vue/es/': '<rootDir>/components', '^ant-design-vue/es/(.*)$': '<rootDir>/components/$1',
}, },
snapshotSerializers: ['<rootDir>/node_modules/jest-serializer-vue'], snapshotSerializers: ['<rootDir>/node_modules/jest-serializer-vue'],
collectCoverage: process.env.COVERAGE === 'true', collectCoverage: process.env.COVERAGE === 'true',

View File

@ -1,17 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders ./components/modal/demo/HookModal.vue correctly 1`] = `
<div><button class="ant-btn ant-btn-default" type="button">
<!----><span>Confirm</span>
</button><button class="ant-btn ant-btn-default" type="button">
<!----><span>With promise</span>
</button><button class="ant-btn ant-btn-dashed" type="button">
<!----><span>Delete</span>
</button><button class="ant-btn ant-btn-dashed" type="button">
<!----><span>With extra props</span>
</button></div>
`;
exports[`renders ./components/modal/demo/async.vue correctly 1`] = ` exports[`renders ./components/modal/demo/async.vue correctly 1`] = `
<div><button class="ant-btn ant-btn-primary" type="button"> <div><button class="ant-btn ant-btn-primary" type="button">
<!----><span>Open Modal with async logic</span> <!----><span>Open Modal with async logic</span>
@ -85,6 +73,29 @@ exports[`renders ./components/modal/demo/fullscreen.vue correctly 1`] = `
</div> </div>
`; `;
exports[`renders ./components/modal/demo/hook-modal.vue correctly 1`] = `
<div class="ant-space ant-space-horizontal ant-space-align-center" style="flex-wrap: wrap; margin-bottom: -8px;">
<div class="ant-space-item" style="margin-right: 8px; padding-bottom: 8px;"><button class="ant-btn ant-btn-default" type="button">
<!----><span>Confirm</span>
</button></div>
<!---->
<div class="ant-space-item" style="margin-right: 8px; padding-bottom: 8px;"><button class="ant-btn ant-btn-default" type="button">
<!----><span>With promise</span>
</button></div>
<!---->
<div class="ant-space-item" style="margin-right: 8px; padding-bottom: 8px;"><button class="ant-btn ant-btn-dashed" type="button">
<!----><span>Delete</span>
</button></div>
<!---->
<div class="ant-space-item" style="margin-right: 8px; padding-bottom: 8px;"><button class="ant-btn ant-btn-dashed" type="button">
<!----><span>With extra props</span>
</button></div>
<!---->
<div class="ant-space-item" style="padding-bottom: 8px;"></div>
<!---->
</div>
`;
exports[`renders ./components/modal/demo/info.vue correctly 1`] = ` exports[`renders ./components/modal/demo/info.vue correctly 1`] = `
<div class="ant-space ant-space-horizontal ant-space-align-center" style="flex-wrap: wrap; margin-bottom: -8px;"> <div class="ant-space ant-space-horizontal ant-space-align-center" style="flex-wrap: wrap; margin-bottom: -8px;">
<div class="ant-space-item" style="margin-right: 8px; padding-bottom: 8px;"><button class="ant-btn ant-btn-default" type="button"> <div class="ant-space-item" style="margin-right: 8px; padding-bottom: 8px;"><button class="ant-btn ant-btn-default" type="button">

View File

@ -98,25 +98,17 @@ describe('Modal.confirm triggers callbacks correctly', () => {
}); });
it('trigger onCancel once when click on cancel button', async () => { it('trigger onCancel once when click on cancel button', async () => {
const arr = ['info']; const onCancel = jest.fn();
for (let type of arr) { const onOk = jest.fn();
Modal[type]({ await open({
title: 'title', title: 'title',
content: 'content', content: 'content',
}); onCancel,
await sleep(); onOk,
expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(1); });
// $$('.ant-btn')[0].click(); await sleep();
// await sleep(2000); $$('.ant-btn')[0].click();
// expect($$(`.ant-modal-confirm-${type}`)).toHaveLength(0); expect(onCancel.mock.calls.length).toBe(1);
} expect(onOk.mock.calls.length).toBe(0);
}); });
// it('should render title', async () => {
// open({
// title: () => <span>title</span>,
// });
// await sleep();
// expect($$('.ant-modal-confirm-title')[0].innerHTML).toBe('<span>title</span>');
// });
}); });

View File

@ -17,25 +17,25 @@ Use `Modal.useModal` to get `contextHolder` with context accessible issue.
</docs> </docs>
<template> <template>
<div> <a-space wrap>
<a-button @click="showConfirm">Confirm</a-button> <a-button @click="showConfirm">Confirm</a-button>
<a-button @click="showPromiseConfirm">With promise</a-button> <a-button @click="showPromiseConfirm">With promise</a-button>
<a-button type="dashed" @click="showDeleteConfirm">Delete</a-button> <a-button type="dashed" @click="showDeleteConfirm">Delete</a-button>
<a-button type="dashed" @click="showPropsConfirm">With extra props</a-button> <a-button type="dashed" @click="showPropsConfirm">With extra props</a-button>
<contextHolder /> <contextHolder />
</div> </a-space>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { Modal } from 'ant-design-vue'; import { Modal } from 'ant-design-vue';
import { ExclamationCircleOutlined } from '@ant-design/icons-vue'; import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import { createVNode } from 'vue'; import { h } from 'vue';
const [modal, contextHolder] = Modal.useModal(); const [modal, contextHolder] = Modal.useModal();
const showConfirm = () => { const showConfirm = () => {
modal.confirm({ modal.confirm({
title: 'Do you Want to delete these items?', title: 'Do you Want to delete these items?',
icon: createVNode(ExclamationCircleOutlined), icon: h(ExclamationCircleOutlined),
content: createVNode('div', { style: 'color:red;' }, 'Some descriptions'), content: h('div', { style: 'color:red;' }, 'Some descriptions'),
onOk() { onOk() {
console.log('OK'); console.log('OK');
}, },
@ -48,7 +48,7 @@ const showConfirm = () => {
const showDeleteConfirm = () => { const showDeleteConfirm = () => {
modal.confirm({ modal.confirm({
title: 'Are you sure delete this task?', title: 'Are you sure delete this task?',
icon: createVNode(ExclamationCircleOutlined), icon: h(ExclamationCircleOutlined),
content: 'Some descriptions', content: 'Some descriptions',
okText: 'Yes', okText: 'Yes',
okType: 'danger', okType: 'danger',
@ -64,7 +64,7 @@ const showDeleteConfirm = () => {
const showPropsConfirm = () => { const showPropsConfirm = () => {
modal.confirm({ modal.confirm({
title: 'Are you sure delete this task?', title: 'Are you sure delete this task?',
icon: createVNode(ExclamationCircleOutlined), icon: h(ExclamationCircleOutlined),
content: 'Some descriptions', content: 'Some descriptions',
okText: 'Yes', okText: 'Yes',
okType: 'danger', okType: 'danger',
@ -84,7 +84,7 @@ const showPropsConfirm = () => {
function showPromiseConfirm() { function showPromiseConfirm() {
modal.confirm({ modal.confirm({
title: 'Do you want to delete these items?', title: 'Do you want to delete these items?',
icon: createVNode(ExclamationCircleOutlined), icon: h(ExclamationCircleOutlined),
content: 'When clicked the OK button, this dialog will be closed after 1 second', content: 'When clicked the OK button, this dialog will be closed after 1 second',
async onOk() { async onOk() {
try { try {

View File

@ -32,7 +32,7 @@ import Width from './width.vue';
import Fullscreen from './fullscreen.vue'; import Fullscreen from './fullscreen.vue';
import ButtonProps from './button-props.vue'; import ButtonProps from './button-props.vue';
import modalRenderVue from './modal-render.vue'; import modalRenderVue from './modal-render.vue';
import HookModal from './HookModal.vue'; import HookModal from './hook-modal.vue';
import CN from '../index.zh-CN.md'; import CN from '../index.zh-CN.md';
import US from '../index.en-US.md'; import US from '../index.en-US.md';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';

View File

@ -1,6 +1,6 @@
import type { ComputedRef, Ref } from 'vue'; import type { Ref } from 'vue';
import { isRef, unref, computed, defineComponent, ref, watch } from 'vue'; import { isRef, unref, computed, defineComponent, shallowRef, watch } from 'vue';
import type { VueNode } from '../../_util/type'; import type { MaybeRef, VueNode } from '../../_util/type';
import type { ModalFuncProps } from '../Modal'; import type { ModalFuncProps } from '../Modal';
import type { HookModalRef } from './HookModal'; import type { HookModalRef } from './HookModal';
import type { ModalStaticFunctions } from '../confirm'; import type { ModalStaticFunctions } from '../confirm';
@ -12,16 +12,17 @@ import destroyFns from '../destroyFns';
let uuid = 0; let uuid = 0;
interface ElementsHolderRef { interface ElementsHolderRef {
addModal: (modal: ComputedRef<JSX.Element>) => () => void; addModal: (modal: () => JSX.Element) => () => void;
} }
const ElementsHolder = defineComponent({ const ElementsHolder = defineComponent({
name: 'ElementsHolder', name: 'ElementsHolder',
inheritAttrs: false, inheritAttrs: false,
setup(_, { expose }) { setup(_, { expose }) {
const modals = ref<ComputedRef<JSX.Element>[]>([]); const modals = shallowRef<(() => JSX.Element)[]>([]);
const addModal = (modal: ComputedRef<JSX.Element>) => { const addModal = (modal: () => JSX.Element) => {
modals.value.push(modal); modals.value.push(modal);
modals.value = modals.value.slice();
return () => { return () => {
modals.value = modals.value.filter(currentModal => currentModal !== modal); modals.value = modals.value.filter(currentModal => currentModal !== modal);
}; };
@ -29,11 +30,11 @@ const ElementsHolder = defineComponent({
expose({ addModal }); expose({ addModal });
return () => { return () => {
return <>{modals.value.map(modal => modal.value)}</>; return modals.value.map(modal => modal());
}; };
}, },
}); });
export type ModalFuncWithRef = (props: Ref<ModalFuncProps> | ModalFuncProps) => { export type ModalFuncWithRef = (props: MaybeRef<ModalFuncProps>) => {
destroy: () => void; destroy: () => void;
update: (configUpdate: ModalFuncProps) => void; update: (configUpdate: ModalFuncProps) => void;
}; };
@ -42,9 +43,9 @@ function useModal(): readonly [
Omit<ModalStaticFunctions<ModalFuncWithRef>, 'warn'>, Omit<ModalStaticFunctions<ModalFuncWithRef>, 'warn'>,
() => VueNode, () => VueNode,
] { ] {
const holderRef = ref<ElementsHolderRef>(null); const holderRef = shallowRef<ElementsHolderRef>(null);
// ========================== Effect ========================== // ========================== Effect ==========================
const actionQueue = ref([]); const actionQueue = shallowRef([]);
watch( watch(
actionQueue, actionQueue,
() => { () => {
@ -65,10 +66,10 @@ function useModal(): readonly [
const getConfirmFunc = (withFunc: (config: ModalFuncProps) => ModalFuncProps) => const getConfirmFunc = (withFunc: (config: ModalFuncProps) => ModalFuncProps) =>
function hookConfirm(config: Ref<ModalFuncProps> | ModalFuncProps) { function hookConfirm(config: Ref<ModalFuncProps> | ModalFuncProps) {
uuid += 1; uuid += 1;
const open = ref(true); const open = shallowRef(true);
const modalRef = ref<HookModalRef>(null); const modalRef = shallowRef<HookModalRef>(null);
const configRef = ref(unref(config)); const configRef = shallowRef(unref(config));
const updateConfig = ref({}); const updateConfig = shallowRef({});
watch( watch(
() => config, () => config,
val => { val => {
@ -78,9 +79,17 @@ function useModal(): readonly [
}); });
}, },
); );
const destroyAction = (...args: any[]) => {
open.value = false;
const triggerCancel = args.some(param => param && param.triggerCancel);
if (configRef.value.onCancel && triggerCancel) {
configRef.value.onCancel(() => {}, ...args.slice(1));
}
};
// eslint-disable-next-line prefer-const // eslint-disable-next-line prefer-const
let closeFunc: Function | undefined; let closeFunc: Function | undefined;
const modal = computed(() => ( const modal = () => (
<HookModal <HookModal
key={`modal-${uuid}`} key={`modal-${uuid}`}
config={withFunc(configRef.value)} config={withFunc(configRef.value)}
@ -91,7 +100,7 @@ function useModal(): readonly [
closeFunc?.(); closeFunc?.();
}} }}
/> />
)); );
closeFunc = holderRef.value?.addModal(modal); closeFunc = holderRef.value?.addModal(modal);
@ -99,14 +108,6 @@ function useModal(): readonly [
destroyFns.push(closeFunc); destroyFns.push(closeFunc);
} }
const destroyAction = (...args: any[]) => {
open.value = false;
const triggerCancel = args.some(param => param && param.triggerCancel);
if (configRef.value.onCancel && triggerCancel) {
configRef.value.onCancel(() => {}, ...args.slice(1));
}
};
const updateAction = (newConfig: ModalFuncProps) => { const updateAction = (newConfig: ModalFuncProps) => {
configRef.value = { configRef.value = {
...configRef.value, ...configRef.value,