React 로직에서 Custom Hook으로 Media Queries를 처리하는 방법
Source: Dev.to
소개
CSS 미디어 쿼리는 화면 너비에 따라 글꼴 크기, 패딩, 그리드 레이아웃 등을 바꾸는 데 아주 유용합니다.
하지만 논리나 구조를 바꿔야 할 때는 어떻게 할까요?
예를 들어, 데스크톱에서는 왼쪽에 고정된 Sidebar가 필요하고, 모바일에서는 같은 사이드바가 버튼을 클릭했을 때 슬라이드되는 Sheet 혹은 Modal이어야 할 수 있습니다. display: none만으로는 컴포넌트가 다르게 동작하기 때문에 해결할 수 없습니다. 여기서 JavaScript 미디어 쿼리가 등장합니다.
이 글에서는 React 컴포넌트 안에서 화면 크기를 직접 감지할 수 있는 가벼운 커스텀 훅 useMediaQuery를 소개합니다.
“CSS‑Only” 숨김의 문제점
흔히 하는 실수는 모바일과 데스크톱 컴포넌트를 모두 렌더링하고, CSS로 하나를 숨기는 것입니다.
{/* This is bad for performance! */}
이 방식은 React가 두 컴포넌트를 모두 렌더링하고, 효과와 로직을 실행하기 때문에 DOM이 불필요하게 커집니다. 사용자가 볼 수 없더라도 말이죠.
해결책: useMediaQuery 훅
React 훅 안에서 네이티브 window.matchMedia API를 사용하면 화면 상태를 효율적으로 추적할 수 있습니다.
import { useEffect, useState } from "react";
export function useMediaQuery(query: string): boolean {
const [matches, setMatches] = useState(false);
useEffect(() => {
const media = window.matchMedia(query);
// 매치 여부가 다르면 즉시 상태 업데이트
if (media.matches !== matches) {
setMatches(media.matches);
}
// 이후 변화에 대한 리스너
const listener = () => setMatches(media.matches);
// 최신 브라우저는 matchMedia에 addEventListener 사용
media.addEventListener("change", listener);
// 언마운트 시 리스너 정리
return () => media.removeEventListener("change", listener);
}, [matches, query]);
return matches;
}
작동 원리
window.matchMedia(query)– 문서가 미디어 쿼리 문자열(예:(max-width: 768px))에 일치하는지 확인합니다.useState– 결과(true또는false)를 로컬 상태에 저장합니다.- 이벤트 리스너 –
change이벤트를 듣고, 사용자가 창 크기를 조절하면 상태가 자동으로 업데이트됩니다.
실제 예시: Sidebar vs. Sheet
이제 이 훅을 사용해 “Sidebar vs. Sheet” 문제를 해결해 보겠습니다. 디바이스에 따라 완전히 다른 UI 구조를 렌더링하고자 합니다.
import { useState } from "react";
import { useMediaQuery } from "./hooks/use-media-query";
export function CourseSidebar({
course,
currentLessonId,
onLessonSelect,
collapsed = false,
onToggle,
}: Props) {
const [isSheetOpen, setIsSheetOpen] = useState(false);
// 여기서 브레이크포인트 정의
const isMobile = useMediaQuery("(max-width: 768px)");
// 1. 모바일 뷰 (Sheet/Modal) 렌더링
if (isMobile) {
return (
<Sheet open={isSheetOpen} onOpenChange={setIsSheetOpen}>
<SheetTrigger>
<Button>Menu</Button>
</SheetTrigger>
<SheetContent>
{/* Your Menu Items */}
</SheetContent>
</Sheet>
);
}
// 2. 데스크톱 뷰 (표준 Sidebar) 렌더링
return (
<aside>
{/* Your Menu Items */}
</aside>
);
}
왜 이것이 더 나은가
if (isMobile)을 사용함으로써 조건부 렌더링을 수행합니다:
- 모바일에서는 데스크톱 사이드바가 DOM에 전혀 렌더링되지 않습니다.
- 데스크톱에서는 모바일
Sheet코드가 무시됩니다.
이렇게 하면 애플리케이션이 가볍고 빠르게 유지되며, 데스크톱 뷰에서 모바일 이벤트 리스너가 불필요하게 작동하는 버그를 방지할 수 있습니다.
요약
CSS는 스타일링을 위한 것이고, JavaScript는 로직을 위한 것입니다. 뷰포트에 따라 렌더링을 바꿔야 할 때는 window.matchMedia를 활용하세요. 이 간단한 useMediaQuery 훅은 CSS 브레이크포인트와 React 컴포넌트 로직 사이의 간극을 메워줍니다.
자세한 내용은 my website 를 방문해 보세요.