overlay-kit 소개
overlay-kit
은 React에서 모달, 팝업, 다이얼로그 같은 오버레이를 선언적으로 관리하기 위한 라이브러리예요.
복잡한 상태 관리나 불필요한 이벤트 핸들링 없이, 효율적으로 오버레이를 구현할 수 있어요.
기능 모아보기
선언적 API
- 오버레이 UI와 동작을 간단한 코드로 선언적으로 정의할 수 있어요.
- 상태 관리와 이벤트 핸들링 코드가 줄어들어 개발이 더 쉬워져요.
Promise 기반 API
- 오버레이의 결과를
Promise
로 반환할 수 있어요. - 사용자의 입력 결과를 처리하거나, 비동기 로직과 결합할 수 있어요.
확장 가능한 컴포넌트
- 원하는 오버레이 컴포넌트를 자유롭게 정의할 수 있어요.
- 다양한 UI 라이브러리와 함께 활용할 수 있어요.
overlay-kit 사용하기
간단한 예시로 overlay-kit
의 사용법을 알아볼게요.
오버레이
화면 위에 렌더링되는 컴포넌트라면 무엇이든 오버레이가 될 수 있어요.
MUI의 Dialog
컴포넌트를 활용해 간단한 확인 다이얼로그를 만들어볼게요.
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle';
import DialogActions from '@mui/material/DialogActions';
export function ConfirmDialog({ isOpen, close }) {
return (
<Dialog open={isOpen} onClose={close}>
<DialogTitle>정말로 계속하시겠어요?</DialogTitle>
<DialogActions>
<Button onClick={close}>아니요</Button>
<Button onClick={close}>네</Button>
</DialogActions>
</Dialog>
);
}
오버레이 위치 설정
오버레이는 화면에 적절한 위치에서 렌더링돼야 해요. 일반적으로 앱 루트에 렌더링하는 것이 좋아요.
OverlayProvider
를 사용해 렌더링 위치를 지정할 수 있어요.
import { OverlayProvider } from 'overlay-kit';
import { createRoot } from 'react-dom/client';
export const Example = () => {
return (
<OverlayProvider>
<App />
</OverlayProvider>
);
};
const root = createRoot(document.getElementById('root'));
root.render(<Example />);
오버레이 열기
overlay.open을 사용하면 오버레이를 쉽게 열고 닫을 수 있어요. 위에서 정의한 ConfirmDialog
를 열어볼게요.
import { OverlayProvider, overlay } from 'overlay-kit'; import Button from '@mui/material/Button'; import { ConfirmDialog } from './confirm-dialog'; function App() { return ( <Button onClick={() => { overlay.open(({ isOpen, close }) => { return <ConfirmDialog isOpen={isOpen} close={close} />; }); }} > Confirm Dialog 열기 </Button> ); } export const Example = () => { return ( <OverlayProvider> <App /> </OverlayProvider> ); };
비동기 오버레이 열기
overlay.openAsync
는 Promise
로 결과를 반환해요. 사용자가 다이얼로그에서 “네”를 선택했을 때 결과를 반환하도록 수정해볼게요.
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle';
import DialogActions from '@mui/material/DialogActions';
export function ConfirmDialog({ isOpen, close, confirm }) {
return (
<Dialog open={isOpen} onClose={close}>
<DialogTitle>정말로 계속하시겠어요?</DialogTitle>
<DialogActions>
<Button onClick={close}>아니요</Button>
<Button onClick={confirm}>네</Button>
</DialogActions>
</Dialog>
);
}
overlay.openAsync
를 사용해 비동기 동작을 처리해볼게요.
import { useState } from 'react'; import { OverlayProvider, overlay } from 'overlay-kit'; import Button from '@mui/material/Button'; import { ConfirmDialog } from './confirm-dialog'; function App() { const [result, setResult] = useState<boolean>(); return ( <> <p>result: {result ? 'Y' : 'N'}</p> <Button onClick={async () => { const result = await overlay.openAsync(({ isOpen, close }) => { return <ConfirmDialog isOpen={isOpen} close={() => close(false)} confirm={() => close(true)} />; }); setResult(result); }} > Confirm Dialog 열기 </Button> </> ); } export const Example = () => { return ( <OverlayProvider> <App /> </OverlayProvider> ); };
오버레이 닫기
overlay.open
과 overlay.openAsync
는 콜백 함수의 인자로 close
와 unmount
함수를 제공해요.
이 두 함수는 모두 오버레이를 닫는 역할을 하지만, 동작 방식에 차이가 있어요.
close
와 unmount
차이점
예제를 통해 동작 차이를 확인해볼게요.
import { OverlayProvider, overlay } from 'overlay-kit'; import Button from '@mui/material/Button'; import { ConfirmDialog } from './confirm-dialog'; function App() { return ( <> <Button onClick={() => { overlay.open( ({ isOpen, close }) => { return <ConfirmDialog isOpen={isOpen} close={close} />; }, { overlayId: 'close-overlay' } ); }} > close로 오버레이 닫기 </Button> <Button onClick={() => { overlay.open( ({ isOpen, unmount }) => { return <ConfirmDialog isOpen={isOpen} close={unmount} />; }, { overlayId: 'unmount-overlay' } ); }} > unmount로 오버레이 닫기 </Button> </> ); } export const Example = () => { return ( <OverlayProvider> <App /> </OverlayProvider> ); };
-
close를 사용한 경우
- 닫기 애니메이션이 실행되고, 상태(count)가 유지돼요.
- 다시 열었을 때 이전 상태가 복원돼요.
-
unmount를 사용한 경우
- 즉시 메모리에서 제거되며, 닫기 애니메이션은 생략돼요.
- 다시 열었을 때 상태가 초기화돼요.
이러한 차이가 발생하는 이유는 close
를 사용할 때 오버레이가 닫히는 애니메이션을 보여주기 위해서 메모리에 계속 남겨두기 때문이에요.
닫기 애니메이션이 필요하다면 close
함수를 먼저 사용 후, unmount
함수를 사용해야 해요.
오버레이 메모리 해제
close
함수는 닫기 애니메이션을 보여준 뒤에도 메모리에 오버레이 정보가 남아있어요. 이런 이유로 많은 오버레이를 동시에 열고 닫는 경우 메모리 누수가 발생할 수 있어요.
이를 방지하려면 닫기 애니메이션이 끝난 뒤 unmount
를 호출해 오버레이를 메모리에서 해제해야 해요.
onExit
prop과 같이 오버레이가 종료된 후 실행되는 콜백 함수가 있다면 unmount
함수를 전달해서 메모리를 해제할 수 있어요.
MUI는 onExit
prop을 제공하지 않지만, React의 useEffect
의 cleanup
함수를 사용하면 손쉽게 오버레이가 닫힐 때 자동으로 메모리에서 해제할 수 있어요.
import { useEffect } from 'react';
import Button from '@mui/material/Button';
import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle';
import DialogActions from '@mui/material/DialogActions';
export function ConfirmDialog({ isOpen, close, onExit }) {
useEffect(() => {
return () => onExit();
}, []);
return (
<Dialog open={isOpen} onClose={close}>
<DialogTitle>정말로 계속하시겠어요?</DialogTitle>
<DialogActions>
<Button onClick={close}>아니요</Button>
<Button onClick={close}>네</Button>
</DialogActions>
</Dialog>
);
}
import { OverlayProvider, overlay } from 'overlay-kit'; import Button from '@mui/material/Button'; import { ConfirmDialog } from './confirm-dialog'; function App() { return ( <> <Button onClick={() => { overlay.open(({ isOpen, close, unmount }) => { return <ConfirmDialog isOpen={isOpen} close={close} onExit={unmount} />; }); }} > Confirm Dialog 열기 </Button> </> ); } export const Example = () => { return ( <OverlayProvider> <App /> </OverlayProvider> ); };