指令示调起一个弹窗Vue和React版本(未完成版)

该功能在Vue、React版本实现起来思路一致。

核心思路

  • 使用一个函数,可以创建组件实例
  • 将新创建的实例,挂载到 DOM 上
  • 如果有特殊场景,需要将上下文进行绑定(本文内实现的暂未绑定上下文)

TODO: ant-design 在新版本实现了可以绑定上下文的 hooks,Modal.useModal,具体还没细看👀,后续有空再看看。

Vue 实现

  1. 首先定义一个 CustomModal.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<template>
<div class="modal-container" v-if="visible">
<div class="modal-content">
<slot></slot>
<button @click="closeModal" class="close-btn">Close</button>
</div>
</div>
</template>

<script setup>
import { ref, defineExpose, watch } from 'vue';

const visible = ref(false);

const openModal = () => {
visible.value = true;
};

const closeModal = () => {
visible.value = false;
};

watch(visible, val => {
console.log('%c [custom modal val ]-24', 'font-size:13px; background:#1c4295; color:#6086d9;', val)
})

defineExpose({
visible,
openModal,
closeModal,
})
</script>

<style scoped>
.modal-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 999;
}

.modal-content {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}

.close-btn {
background-color: #4299e1;
color: white;
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>
  1. 定义指令 showModal 方法,写在 modal.js 中,核心代码在这里
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import { createApp, nextTick, watch } from 'vue';

export function showModal(component) {
// 创建一个 Vue 示例
const app = createApp(component);
// 创建一个容器,用来放 Modal 组件
const root = document.createElement('div');

// 将内容挂载到页面
document.body.appendChild(root);

// 将 Modal 组件挂载到页面上
const instance = app.mount(root);
// 手动调用组件内部函数,展示组件
instance.openModal();

// 定义卸载函数
const unmount = () => {
app.unmount();
document.body.removeChild(root);
};

// 监听 Modal 组件是否关闭
watch(
() => instance.visible,
() => {
nextTick(unmount);
}
);

return {
instance,
unmount,
};
}

  1. 在调用处使用
1
2
3
4
5
6
7
8
9
10
11
12
<template>
<button @click="handleOpen">打开模态框</button>
</template>

<script setup>
import { showModal } from './modal.js'
import CustomModal from './CustomModal.vue'

function handleOpen() {
showModal(CustomModal)
}
</script>

React 实现

一样的步骤

  1. 首先实现一个 CustomModal.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import React, { forwardRef, useImperativeHandle, useState } from 'react';

interface ModalProps {
children?: React.ReactNode;
defaultVisible?: boolean;
onAfterClose?: () => void
}

const Modal: React.FC<ModalProps> = forwardRef((props, ref) => {
const { children, defaultVisible, onAfterClose } = props;
const [isVisible, setIsVisible] = useState(defaultVisible ?? true);

const openModal = () => {
setIsVisible(true);
};

const closeModal = () => {
setIsVisible(false);
onAfterClose?.()
};

useImperativeHandle(ref, () => {
return {
openModal,
closeModal,
}
});

return (
<div>
{isVisible && (
<div
style={{
position: 'fixed',
top: 0,
left: 0,
width: '100%',
height: '100%',
backgroundColor: 'rgba(0, 0, 0, 0.5)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
zIndex: 999,
}}
>
<div
style={{
backgroundColor: 'white',
padding: '20px',
borderRadius: '5px',
boxShadow: '0 2px 4px rgba(0, 0, 0, 0.2)',
}}
>
{children}
<button
onClick={closeModal}
style={{
backgroundColor: '#4299e1',
color: 'white',
padding: '8px 16px',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
}}
>
Close
</button>
</div>
</div>
)}
</div>
);
});

export default Modal;
  1. 定义指令 showModal 方法,写在 modal.ts 中,核心代码在这里
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { createRoot } from "react-dom/client"

export function showModal (node: React.ReactElement) {
// 一样的流程,新创建一个 dom
const app = document.createElement('div')
// 挂载到文档中
document.body.appendChild(app)
// 创建一个 React 实例
const root = createRoot(app)
// 进行渲染
root.render(node)

return {
// 卸载函数
unmount: () => {
root.unmount()
document.body.removeChild(app)
}
}
}
  1. 在调用处使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { showModal } from './modal.ts';
import CustomModal from './CustomModal.tsx';

function App () {

function handleOpenModal() {
const modal = showModal(
<CustomModal
onAfterClose={() => {
{/* 关闭后进行卸载 */}
modal.unmount();
}}
/>
);
}

return (
<div>
<button onClick={handleOpenModal}>打开模态框</button>
</div>
)
}

参考

vant 实现源码(Vue)

ant-design 实现源码(React)