回头看了看几个月前搓的 Dialog
组件,代码十分糅杂,一时间不知道如何评价
不过当时本着学习 React 的心态,第一目标是实现,或许没考虑那么多 🫢
重构后的代码
import React, { ReactNode, useCallback, useEffect, useReducer, useState } from 'react';
import { createRoot } from 'react-dom/client';
import { AnimatePresence, motion } from 'framer-motion';
import { Icon } from '@iconify/react';
function DialogContent({ node, instance }: { node: ReactNode; instance: DialogInstance }) {
const [show, setShow] = useState(true);
const forArr = () => {
const index = dialogArr.indexOf(instance);
if (index > -1) {
dialogArr.splice(index, 1);
}
if (dialogArr.length < 1) {
document.removeEventListener('keydown', onEsc);
}
};
const close = useCallback(() => {
forArr();
setShow(false);
setTimeout(() => {
instance.div.remove();
}, 200);
}, [instance]);
instance.close = close;
return (
<AnimatePresence>
{show && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
className="fixed top-0 left-0 h-full w-full bg-white bg-opacity-20 grid place-content-center"
onClick={close}
>
<motion.div
className="min-w-[500px] min-h-[400px] bg-white rounded-md flex flex-col"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }}
transition={{ duration: 0.2, ease: 'easeInOut' }}
onClick={e => e.stopPropagation()}
>
<div className="border-b border-b-solid border-b-gray-300 p-[8px_16px] flex">
<div className="flex gap-[5px] items-center">
<span className="text-[18px]">🧀</span>
</div>
<div className="ml-auto">
<motion.button whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.9 }} onClick={close}>
<Icon icon="heroicons:x-circle-20-solid" className="text-[25px] text-red-500" />
</motion.button>
</div>
</div>
{node}
</motion.div>
</motion.div>
)}
</AnimatePresence>
);
}
const dialogArr: DialogInstance[] = [];
type DialogInstance = {
div: HTMLDivElement;
close?: () => void;
};
function show(content: ReactNode) {
const instance: DialogInstance = {
div: document.createElement('div'),
};
createRoot(instance.div).render(<DialogContent node={content} instance={instance} />);
document.body.appendChild(instance.div);
dialogArr.push(instance);
document.addEventListener('keydown', onEsc);
return instance;
}
function onEsc(event: KeyboardEvent) {
if (event.key === 'Escape') {
const instance = dialogArr.pop();
instance?.close?.();
}
}
export default {
show,
};
本质上的操作逻辑并无变化,优化了实现过程
排除了之前的用于处理 ESC 监听 hooks
,改为了在外部维护对象,调用方可以依靠 show
返回的对象来关闭弹窗 🤪
空空如也!