어쩌다 알게 된 ƪ(•̃͡•̃͡ ƪ

팝업 컴포넌트 2차 (외부 클릭시, esc 닫힘_useRef 사용) 본문

개발/🔵 React-TypeScript

팝업 컴포넌트 2차 (외부 클릭시, esc 닫힘_useRef 사용)

비니_ 2025. 9. 18. 15:27
728x90

 

 

팝업 컴포넌트 1차

"use client";import { Button } from "./Buttons";interface DefaultPopupProps{ text: string; isOpen: boolean; setIsOpen: React.Dispatch>;}const DefaultPopup = ({ text, isOpen, setIsOpen}: DefaultPopupProps) => { return ( setIsOpen(true)} /> {isOpen && ( setI

dazzle-bini.tistory.com

 

+ 팝업 외부 클릭시 팝업 닫힘

// 외부 클릭시
const outsideRef = useRef<HTMLDivElement>(null);
useEffect(() => {
    const handleClickOutside = (e: MouseEvent) => {
        if(outsideRef.current && !outsideRef.current.contains(e.target as Node)){
            setIsOpen(false);
        }
    }

    document.addEventListener('mousedown', handleClickOutside);
    return() => {
        document.removeEventListener('mousedown', handleClickOutside);
    }
}, [setIsOpen]);

 

💻 useRef

=> DOM 요소를 직접 참조 가능

✔ contains() → 클릭한 요소가 현재 DOM 안에 있는지 체크

current 속성 → 실제 HTMLElement

 

💻 MouseEvent

const handleClickOutside = (e: MouseEvent) => { ... }

=> e는 마우스 관련 이벤트 정보를 담고 있는 객체

 

💻 outsideRef.current && !outsideRef.current.contains(e.target as Node)

✔ outsideRef.current 가 null인지 아닌지 판단

=> 렌더링 전: null / 렌더링 후: .pop_wrap을 가리킴

 outsideRef.current.contains(e.target as Node)

=> true: 내부 클릭 / false: 외부 클릭

 

💻 mousedown

=> 등록한 이벤트는 cleanup으로 지워주기

=> 지우지 않으면 버그, 성능 문제, 메모리 누수 생길 수 있음

document.addEventListener('mousedown', handleClickOutside);

=> 마우스 버튼을 눌렀을 때 발생

return () => { document.removeEventListener(...); }

=> cleanup, 이벤트 지우기

 

 

+ esc 눌렀을 시 팝업 닫힘

const outsideRef = useRef<HTMLDivElement>(null);
useEffect(() => {
    // esc 눌렀을 시
    const handleEsc = (e: KeyboardEvent) => {
        if(e.key === "Escape"){
            setIsOpen(false);
        }
    }

    document.addEventListener('keydown', handleEsc);
    return() => {
        document.removeEventListener('keydown', handleEsc);
    }
}, [setIsOpen]);

 

💻 무슨 키를 눌렀는지 console 찍기

  const handleEsc = (e: KeyboardEvent) => {
        console.log(e.key);      // 어떤 키인지 (예: "Escape", "Enter", "a")
        console.log(e.code);    // 물리적 키보드 위치 (예: "KeyA", "Escape")

     }

 

 

전체 코드

"use client";

import { useEffect, useRef } from "react";
import { Button } from "./Buttons";

interface DefaultPopupProps{
    text: string;
    isOpen: boolean;
    setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
}

const DefaultPopup = ({
    text,
    isOpen,
    setIsOpen
}: DefaultPopupProps) => {

    const outsideRef = useRef<HTMLDivElement>(null);
    useEffect(() => {
        // 외부 클릭시
        const handleClickOutside = (e: MouseEvent) => {
            if(outsideRef.current && !outsideRef.current.contains(e.target as Node)){
                setIsOpen(false);
            }
        }

        // esc 눌렀을 시
        const handleEsc = (e: KeyboardEvent) => {
            if(e.key === "Escape"){
                setIsOpen(false);
            }
        }

        document.addEventListener('mousedown', handleClickOutside);
        document.addEventListener('keydown', handleEsc);
        return() => {
            document.removeEventListener('mousedown', handleClickOutside);
            document.removeEventListener('keydown', handleEsc);
        }
    }, [setIsOpen]);

    return (
        <div className="btn_pop_wrap">
            <Button
                text={text}
                onClick={() => setIsOpen(true)}
            />
            {isOpen && (
                <div className="pop_wrap" ref={outsideRef}>
                    <div className="pop_header">
                        <Button
                            text="X"
                            onClick={() => setIsOpen(false)}
                        />
                    </div>
                    <div className="pop_content">
                        팝업 내용 <br />
                        팝업 내용 <br />
                        팝업 내용 <br />
                        팝업 내용 <br />
                        팝업 내용 <br />
                        팝업 내용 <br />
                        팝업 내용 <br />
                        팝업 내용 <br />
                        팝업 내용 <br />
                        팝업 내용 <br />
                        팝업 내용 <br />
                    </div>
                    <div className="pop_footer">
                        <Button
                            className="blue"
                            text='취소'
                            onClick={() => setIsOpen(false)}
                        />
                        <Button
                            className="red"
                            text='확인'
                        />
                    </div>
                </div>
            )}
        </div>
    )
}

export { DefaultPopup };

 

 

 

 

 

 

 

 

728x90
Comments