[Web] Responsive Web (반응형 웹) 개발하기
1. 개요
웹 프론트를 구현함에 있어 반응형 디자인은 다양한 디바이스 환경에서도 일관된 사용자 경험을 제공하기 위해 꼭 필요한 요소라고 할 수 있다. 진행 중인 웹 프로젝트에서 PC, 태블릿, 모바일 사용자 모두에게 최적화된 화면을 제공하고자 반응형 구현에 대해 많은 고민을 하게 되었다.
단순히 CSS 미디어 쿼리만으로 해결할 수 있는 영역도 있지만, 때로는 자바스크립트 로직으로 화면 크기를 동적으로 처리해야 하는 상황도 발생한다. 이에 따라 useResponsive
라는 사용자 정의 훅(Custom Hook) 을 직접 만들어 화면 정보를 추적하고, 이를 기반으로 컴포넌트의 동작을 제어할 수 있도록 구성하였다.
이 포스팅에서는 해당 사용자 정의 훅의 구조와 구현 방식, 그리고 실제로 어떻게 활용했는지에 대해 정리해보려고 한다.
2. 사용자 정의 훅
React에는 상태 관리와 생명주기 처리를 보다 선언적으로 구현할 수 있도록 도와주는 훅(Hook) 이라는 개념이 존재한다. useState
, useEffect
등과 같은 내장 훅 외에도, 상황에 따라 여러 훅을 조합해 사용자 정의 훅(Custom Hook) 을 만들 수 있다.
커스텀 훅은 단순히 use
로 시작하는 함수이며, 그 안에서 다른 훅들을 사용할 수 있도록 설계된 함수다. 본문에서 소개할 useResponsive
역시 이러한 커스텀 훅으로, 반응형 정보를 다루기 위해 설계되었다.
2.1. 목적
useResponsive
훅은 현재 화면의 너비(width
), 높이(height
), 화면 비율(ratio
), 그리고 디바이스 타입(desktop
, tablet
, mobile
)을 판별해주는 기능을 한다. 컴포넌트 내에서 이를 활용하면 디바이스 환경에 따라 렌더링 방식을 다르게 제어할 수 있도록 구현하였다.
2.2. 코드
"use client";
import { useState, useEffect } from "react";
// 디바이스 타입 정의
type DeviceType = "desktop" | "tablet" | "mobile";
// 반응형 상태 타입 정의
interface ResponsiveState {
width: number; // 현재 화면 너비
height: number; // 현재 화면 높이
ratio: number; // 화면 비율 (가로/세로)
status: DeviceType; // 디바이스 종류
isMobile: boolean; // 모바일 여부
isTablet: boolean; // 태블릿 여부
isDesktop: boolean; // 데스크탑 여부
}
// 반응형 정보를 반환하는 커스텀 훅
const useResponsive = () => {
// 화면 너비를 기준으로 디바이스 상태 반환
const getStatus = (width: number): DeviceType => {
if (width < 768) return "mobile"; // 768px 미만: 모바일
if (width < 1024) return "tablet"; // 768px 이상 1024px 미만: 태블릿
return "desktop"; // 1024px 이상: 데스크탑
};
// 초기 상태 설정
const [state, setState] = useState<ResponsiveState>({
width: 0,
height: 0,
ratio: 0,
status: "desktop",
isMobile: false,
isTablet: false,
isDesktop: true,
});
// 브라우저 사이즈 변경에 반응하여 상태 업데이트
useEffect(() => {
if (typeof window === "undefined") return; // SSR 환경에서는 무시
const handleResize = () => {
const width = window.innerWidth;
const height = window.innerHeight;
const status = getStatus(width); // 현재 디바이스 상태 계산
// 상태 업데이트
setState({
width,
height,
ratio: width / height,
status,
isMobile: status === "mobile",
isTablet: status === "tablet",
isDesktop: status === "desktop",
});
};
// 첫 렌더링 시에도 상태 반영
handleResize();
// 리사이즈 이벤트 리스너 등록
window.addEventListener("resize", handleResize);
// 컴포넌트 언마운트 시 리스너 제거
return () => window.removeEventListener("resize", handleResize);
}, []);
// 현재 반응형 상태 반환
return state;
}
export default useResponsive;
2.3. 예시
해상도 1280 × 720 의 환경에서 useResponsive
훅은 다음의 값을 반환한다.
{
width: 1280,
height: 720,
ratio: 1.7777777778,
status: "desktop",
isMobile: false,
isTablet: false,
isDesktop: true
}
3. 반응형 컴포넌트
이제 이 훅을 실제 컴포넌트에서 어떻게 사용할 수 있는지 살펴보자. 사용하는 방식은 크게 두 가지 패턴으로 나눌 수 있다.
3.1 CSS 클래스 조합 방식
첫 번째 방식은 디바이스 상태에 따라 특정 CSS 클래스를 조건부로 적용하는 방법이다. 간단한 크기 조정이나 스타일 분기라면 이 방식이 가장 깔끔하다.
const MyComponent: React.FC = () => {
const { status } = useResponsive();
return (
<div className={`${styles.container} ${styles[status]}`}></div>
);
}
.container { max-width: 95%; }
.desktop.container { width: 1280px; }
.tablet.container { width: 880px; }
.mobile.container { width: 91.4%; }
3.2 컴포넌트 분기 방식
두 번째는 디바이스별로 아예 별도의 컴포넌트를 렌더링하는 방식이다. 디바이스에 따라 렌더링 구조나 내용이 완전히 달라지는 경우에 적합하다.
const MyComponent: React.FC = () => {
const { isDesktop, isTablet, isMobile } = useResponsive();
return (
<>
{isDesktop && <DesktopMyComponent />}
{isTablet && <TabletMyComponent />}
{isMobile && <MobileMyComponent />}
</>
);
}
선택 기준
- 단순 스타일 차이만 있는 경우 → CSS 클래스 방식
- 구조 자체가 다른 UI가 필요할 경우 → 컴포넌트 분기 방식