useImperativeHandle 이란?
useImperativeHandle is a React Hook that lets you customize the handle exposed as a ref.
useImperativeHandle은 ref로 노출되는 핸들을 사용자가 직접 정의할 수 있게 해주는 React 훅이다.
useImperativeHandle(ref, createHandle, dependencies?)
React에서 외부에서 주입된 prop을 사용하여 모달(또는 레이어)의 열림 상태를 제어
부모 컴포넌트에서 모달의 열림 상태를 관리하고, 이 상태를 모달 컴포넌트에 prop으로 전달
제어 컴포넌트(Controlled Component) 패턴
import React, { useState } from 'react';
import ListModal from './ListModal';
const ParentComponent: React.FC = () => {
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
return (
<div>
<button onClick={() => setIsModalOpen(!isModalOpen)}>
Toggle List
</button>
<ListModal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} />
</div>
);
};
export default ParentComponent;
import React, { useEffect, useRef } from 'react';
interface ListModalProps {
isOpen: boolean;
onClose: () => void;
}
const ListModal: React.FC<ListModalProps> = ({ isOpen, onClose }) => {
const modalRef = useRef<HTMLDivElement>(null);
// 외부 클릭 감지
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (modalRef.current && !modalRef.current.contains(event.target as Node)) {
onClose();
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [onClose]);
if (!isOpen) {
return null;
}
return (
<div ref={modalRef} style={{ position: 'absolute', border: '1px solid black', padding: '10px', background: 'white' }}>
<ul>
<li>List Item 1</li>
<li>List Item 2</li>
<li>List Item 3</li>
</ul>
</div>
);
};
export default ListModal;
import React from 'react';
import ListModal from './ListModal';
const ParentComponent: React.FC = () => {
let modalRef: React.RefObject<ListModal> = React.createRef();
const openModal = () => {
modalRef.current?.openModal();
};
return (
<div>
<button onClick={openModal}>
Open Modal
</button>
<ListModal ref={modalRef} />
</div>
);
};
export default ParentComponent;
import React, { useEffect, useRef, useState, forwardRef, useImperativeHandle } from 'react';
export interface ListModalHandles {
openModal: () => void;
closeModal: () => void;
}
const ListModal = forwardRef<ListModalHandles>((props, ref) => {
const [isOpen, setIsOpen] = useState<boolean>(false);
const modalRef = useRef<HTMLDivElement>(null);
useImperativeHandle(ref, () => ({
openModal: () => setIsOpen(true),
closeModal: () => setIsOpen(false)
}));
// 외부 클릭 감지
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (modalRef.current && !modalRef.current.contains(event.target as Node)) {
setIsOpen(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, []);
if (!isOpen) {
return null;
}
return (
<div ref={modalRef} style={{ position: 'absolute', border: '1px solid black', padding: '10px', background: 'white' }}>
<ul>
<li>List Item 1</li>
<li>List Item 2</li>
<li>List Item 3</li>
</ul>
</div>
);
});
export default ListModal;
`ListModal` 컴포넌트는 내부적으로 자신의 상태(`isOpen`)를 관리합니다.
`useImperativeHandle`과 `forwardRef`를 사용하여 `ListModal` 컴포넌트는 `openModal`과 `closeModal` 메서드를 외부로 노출합니다. 이를 통해 부모 컴포넌트가 이 메서드들을 사용할 수 있습니다.
`ParentComponent`에서는 `modalRef`를 사용하여 `ListModal`의 `openModal` 메서드를 호출할 수 있습니다. 이를 통해 부모 컴포넌트에서 모달을 열 수 있습니다.
이 방법을 사용하면 `ListModal` 컴포넌트의 내부 상태를 유지하면서도, 부모 컴포넌트에서 해당 모달을 열고 닫는 것을 제어할 수 있습니다.
다시 정리하기
부모 컴포넌트에서 ListModal 컴포넌트의 닫힘 여부를 알기 위해서는 ListModal 컴포넌트로부터 어떤 형태의 피드백을 받아야 합니다. 이를 위해 ListModal에 콜백 prop을 추가하여 모달이 닫힐 때 이를 부모 컴포넌트에 알릴 수 있습니다.
예를 들어, ListModal에 onClose라는 콜백 prop을 추가할 수 있습니다. 이 콜백은 모달이 외부 클릭에 의해 닫힐 때 호출되며, 부모 컴포넌트에서 이 상태 변화를 감지할 수 있도록 합니다.
다음은 ListModal 컴포넌트에 onClose 콜백을 추가하는 방법입니다:
import React, { useEffect, useRef, useState, forwardRef, useImperativeHandle } from 'react';
export interface ListModalHandles {
openModal: () => void;
closeModal: () => void;
}
interface ListModalProps {
onClose?: () => void;
children: React.ReactNode;
}
const ListModal = forwardRef<ListModalHandles, ListModalProps>(({ onClose, children }, ref) => {
const [isOpen, setIsOpen] = useState<boolean>(false);
const modalRef = useRef<HTMLDivElement>(null);
useImperativeHandle(ref, () => ({
openModal: () => setIsOpen(true),
closeModal: () => {
setIsOpen(false);
if (onClose) onClose();
}
}));
// 외부 클릭 감지 및 모달 닫기
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (modalRef.current && !modalRef.current.contains(event.target as Node)) {
setIsOpen(false);
if (onClose) onClose();
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [onClose]);
if (!isOpen) {
return null;
}
return (
<div ref={modalRef} style={{ position: 'absolute', border: '1px solid black', padding: '10px', background: 'white' }}>
{children}
</div>
);
});
export default ListModal;
이제 부모 컴포넌트에서 ListModal의 onClose prop을 사용하여 모달이 닫힐 때의 동작을 정의할 수 있습니다:
import React, { useRef } from 'react';
import ListModal from './ListModal';
const ParentComponent: React.FC = () => {
let modalRef = useRef<ListModalHandles>(null);
const handleModalClose = () => {
console.log('Modal has been closed');
// 모달이 닫혔을 때의 추가 동작을
이런 방식으로 컴포넌트를 제어하는 패턴은 "Imperative Handle Pattern" 또는 "Imperative Programming"이라고 합니다. useImperativeHandle과 forwardRef를 사용하여 부모 컴포넌트가 자식 컴포넌트의 메서드나 변수에 직접 접근할 수 있도록 하여, 부모 컴포넌트에서 자식 컴포넌트의 내부 상태를 직접 제어할 수 있게 합니다.
'리액트' 카테고리의 다른 글
useLayoutEffect 훅이란? (1) | 2024.02.05 |
---|---|
[리액트] 별점 컴포넌트 (0) | 2024.01.24 |
리액트에서 dangerouslySetInnerHTML 렌더링하기 (0) | 2023.10.11 |
리액트에서 string 데이터와 ReactElement가 혼합된 데이터 렌더링 하기 (0) | 2023.10.11 |
FileReader로 파일 읽고 업로드한 파일 미리보기 구현 (0) | 2023.09.18 |