该功能在Vue、React版本实现起来思路一致。
核心思路
- 使用一个函数,可以创建组件实例
- 将新创建的实例,挂载到 DOM 上
- 如果有特殊场景,需要将上下文进行绑定(本文内实现的暂未绑定上下文)
Vue 实现
- 首先定义一个
CustomModal.vue
<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>
- 定义指令
showModal
方法,写在modal.js
中,核心代码在这里
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,
};
}
- 在调用处使用
<template>
<button @click="handleOpen">打开模态框</button>
</template>
<script setup>
import { showModal } from './modal.js'
import CustomModal from './CustomModal.vue'
function handleOpen() {
showModal(CustomModal)
}
</script>
React 实现
一样的步骤
- 首先实现一个
CustomModal.tsx
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;
- 定义指令
showModal
方法,写在modal.ts
中,核心代码在这里
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)
}
}
}
- 在调用处使用
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>
)
}