React 로직에서 Custom Hook으로 Media Queries를 처리하는 방법

발행: (2025년 12월 11일 오전 10:05 GMT+9)
5 min read
원문: Dev.to

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 를 방문해 보세요.

Back to Blog

관련 글

더 보기 »

내부 구조: React

소개 나는 React를 사용하기 시작한 순간부터 이것을 하고 싶었다: 그것이 어떻게 작동하는지 이해하고 싶었다. 이것은 소스 코드를 세밀하게 검토하는 것이 아니다. In...

React를 이용한 계산기

오늘 나는 React를 사용한 계산기 연습 프로젝트 중 하나를 완료했습니다. 이 React Calculator 애플리케이션은 기본 산술 연산을 수행합니다. 버튼을 지원합니다.