React 프로젝트에서 모달 컴포넌트를 구현할 때, UI의 구조를 더 깔끔하게 유지하고 접근성을 높이기 위해 React Portals와 Refs를 활용하는 방법을 알아보겠습니다. 이번 글에서는 강좌에서 소개된 코드를 바탕으로 이 두 가지 기능을 효과적으로 사용하는 방법을 설명합니다.
1. React Portals의 필요성
React에서 모달을 구현할 때, 모달 컴포넌트가 다른 요소들 위에 시각적으로 떠 있도록 설정하는 경우가 많습니다. 일반적으로 모달 컴포넌트는 div 태그 안에 중첩되어 출력됩니다.
하지만 이렇게 되면 스타일 문제나 접근성 문제를 초래할 수 있습니다. 이때 React Portals를 사용하면 모달을 실제 DOM의 더 상위 레벨, 예를 들어 body 바로 아래에 렌더링할 수 있습니다. 이렇게 하면 스타일링 및 접근성 문제를 해결할 수 있습니다.
2. React Portals란 무엇인가?
React Portals는 특정 컴포넌트의 렌더링 위치를 조정할 수 있는 기능입니다. 일반적으로 React 컴포넌트는 그 부모 컴포넌트의 DOM 트리 내부에 렌더링되지만, Portals를 사용하면 특정 컴포넌트의 렌더링 결과를 DOM 트리의 다른 위치에 삽입할 수 있습니다. 이는 모달처럼 화면 전체를 덮는 오버레이 요소를 구현할 때 유용합니다.
예를 들어, 모달 컴포넌트를 페이지의 최상위 레벨에 위치시키고 싶다면 createPortal 함수를 사용하여 이를 구현할 수 있습니다. 이 함수는 JSX 코드와 해당 코드를 렌더링할 DOM 요소를 인수로 받습니다.
3. React Portals 사용 방법
React Portals를 사용하려면 React DOM 라이브러리에서 제공하는 createPortal 함수를 사용해야 합니다. 이 함수는 두 가지 인수를 받습니다. 첫 번째는 렌더링할 JSX 코드이고, 두 번째는 해당 코드를 렌더링할 DOM 요소입니다.
예를 들어, 아래와 같은 코드를 통해 모달을 특정 div 요소에 렌더링할 수 있습니다.
저는 index.html에 이미 아래와 같이 작성해주었고, 해당 id에 연결할 예정입니다.
<div id="modal"></div>
그리고 createPortal을 react-dom에서 불러와서 아래와 같이 작성하였습니다. 제가 만든 모달은 dialog이며, 이를 modal이라는 dom에 위치시키려고 합니다.
import { createPortal } from "react-dom";
const ResultModal = forwardRef(function ResultModal(
{ targetTime, remainingTime, onReset },
ref
) {
const dialog = useRef();
useImperativeHandle(ref, () => {
return {
open() {
dialog.current.showModal();
},
};
});
그리고 return 문에 createPortal을 적어주고, 맨 하단 컴포넌트에 쉼표를 붙여준 뒤 두번째 인자로 modal 의 dom값을 넣어줍니다.
return createPortal(
<div>
// 생략
</div>
,
document.getElementById("modal")
);
});
위 코드에서 createPortal 함수는 모달 컴포넌트를 id="modal"을 가진 div 요소 안에 렌더링합니다.
이는 모달이 콘텐츠 구조의 깊은 중첩에서 벗어나, 더 상위 DOM 레벨에서 출력되도록 합니다.
4. Refs와 useImperativeHandle로 모달 제어하기
모달의 상태를 제어하기 위해 React Refs와 useImperativeHandle을 사용할 수 있습니다. 이 기능을 통해 모달을 열고 닫는 메서드를 외부에서 호출할 수 있습니다. 예를 들어, TimerChallenge 컴포넌트에서 모달을 제어하기 위해 아래와 같은 코드가 사용됩니다.
Refs란?
Refs는 React에서 특정 DOM 요소나 클래스 컴포넌트에 직접 접근할 수 있는 방법을 제공합니다. 주로 DOM 조작이나 포커스 관리, 애니메이션 트리거 등에 사용됩니다.
useImperativeHandle이란?
useImperativeHandle은 React Hook 중 하나로, 부모 컴포넌트가 자식 컴포넌트의 인스턴스 메서드를 사용할 수 있게 해줍니다. 이를 통해 부모 컴포넌트는 자식 컴포넌트의 내부 상태나 메서드를 외부에서 제어할 수 있습니다. 위 예시에서, 모달을 열기 위한 open 메서드를 정의하고 있습니다.
TimerChallenge 컴포넌트의 예제 코드
TimerChallenge 컴포넌트는 ResultModal 컴포넌트를 참조(Ref)하고, 사용자가 버튼을 클릭할 때마다 모달을 열거나 닫도록 제어합니다.
import { useState, useRef } from "react";
import ResultModal from "./ResultModal";
export default function TimerChallenge({ title, targetTime }) {
const timer = useRef();
const dialog = useRef();
const [timeRemaining, setTimeRemaining] = useState(targetTime * 1000);
const timerIsActive = timeRemaining > 0 && timeRemaining < targetTime * 1000;
if (timeRemaining <= 0) {
clearInterval(timer.current);
dialog.current.open();
}
function handleReset() {
setTimeRemaining(targetTime * 1000);
}
function handleStart() {
timer.current = setInterval(() => {
setTimeRemaining((prevTimeRemainig) => prevTimeRemainig - 10);
}, 10);
}
function handleStop() {
dialog.current.open();
clearInterval(timer.current);
}
return (
<>
{title}
{targetTime} second{targetTime > 1 ? "s" : ""}
{timerIsActive ? "Stop" : "Start"} Challenge
{timerIsActive ? "Time is running..." : " Timer inactive"}
</>
);
}
위 코드에서 ResultModal 컴포넌트는 ref를 통해 제어되고, 사용자가 타이머를 중지하거나 시간이 다 되었을 때 모달을 보여주도록 합니다. 이렇게 Refs와 Portals를 결합하여 더 나은 사용자 경험을 제공할 수 있습니다.
'개발 이야기 > frontend' 카테고리의 다른 글
내가 그동안 사용했던 디자인 패턴에 대한 고찰 (1) | 2024.10.08 |
---|---|
MVVM 프레임워크, Flux, Redux 제대로 이해하기 (1) | 2024.08.30 |
자바스크립트는 싱글 스레드 언어다 - 비동기 처리 방식과 Web API (0) | 2024.08.28 |
자바스크립트 콜스택 쌓이는 순서 확인하는 방법 (0) | 2024.08.27 |
일렉트론 package.json 설정 및 외부에서 접근 가능하도록 빌드하기 (0) | 2024.08.22 |
댓글