반응형 웹과 모바일 감지의 중요성
웹 개발에서 반응형 웹 디자인은 이제 필수입니다. 데스크톱, 태블릿, 모바일 등 다양한 디바이스에 맞춘 최적화된 UI는 사용자 경험을 크게 향상하는 중요한 요소입니다.
특히 모바일 사용자의 비율이 증가함에 따라, 화면 크기에 따라 레이아웃과 스타일을 동적으로 변경하는 기능은 더욱 필수적입니다.
이 글에서는 React에서 미디어 쿼리를 활용해 Custom Hook을 만들어 모바일 디바이스를 감지하고, 스타일을 효율적으로 변경하는 useIsMobile
훅을 소개합니다.
모바일을 감지하는 useIsMobile
훅
먼저, 미디어 쿼리를 이용해 모바일 화면을 감지할 수 있습니다.
이걸 React에서 쉽게 사용할 수 있도록 useIsMobile
훅을 만들겠습니다. 이 훅은 컨텍스트 API를 활용해 현재 화면이 모바일인지 아닌지를 알려줍니다.
컨텍스트 API: 전역 상태 관리
앞서 말한 컨텍스트 API가 뭔지 간단히 설명하겠습니다.
컨텍스트 API는 React에서 전역 상태를 관리할 때 사용하는 도구입니다. 컴포넌트 트리를 따라 props를 일일이 전달하지 않고도 상위 컴포넌트에서 하위 컴포넌트로 값을 전달할 수 있게 해줍니다.
특히, 테마, 언어 설정, 그리고 미디어 쿼리에 따른 모바일 여부 같은 전역적으로 사용되는 값을 관리할 때 유용합니다.
1. useIsMobile
훅 만들기
우선, 모바일 여부를 쉽게 감지할 수 있는 훅을 만들겠습니다.useIsMobile
은 컨텍스트를 통해 모바일 상태를 확인하고, 이를 boolean
값으로 반환합니다.
// useIsMobile.ts
import { useContext } from "react";
import { MediaQueryContext } from "@/context/MediaQueryContext";
export const useIsMobile = (): boolean => {
const context = useContext(MediaQueryContext);
if (context === null) {
throw new Error("useIsMobile must be used within a MediaQueryProvider");
}
return context;
};
코드 설명
useContext(MediaQueryContext)
: 컨텍스트 API를 사용해 모바일 여부를 감지합니다.throw new Error
: 이 훅은 MediaQueryProvider 내부에서만 사용되어야 하기 때문에, 만약 컨텍스트가 없으면 에러를 던져 개발자가 잘못된 사용을 했음을 알려줍니다.return context
: 최종적으로 모바일 여부 (true 또는 false)를 반환합니다.
2. 미디어 쿼리를 감지하는 useMEdiaQuery
훅 만들기
이제 미디어 쿼리를 감지하는 로직이 필요합니다. CSS에서 쓰이는 미디어 쿼리와 비슷한 방식으로, 화면 크기를 감지해 화면 넓이가 768px
이하일 경우 모바일로 인식하는 훅을 만들겠습니다.
// useMediaQuery.ts
import { useState, useEffect } from "react";
// query 파라미터를 받아 미디어 쿼리를 감지하는 훅
export const useMediaQuery = (query: string): boolean => {
const [value, setValue] = useState(false); // 화면 크기와 맞는지 여부를 저장하는 상태
useEffect(() => {
// 미디어 쿼리 조건이 변경될 때 실행되는 함수
const onChange = (event: MediaQueryListEvent) => {
setValue(event.matches); // 쿼리가 일치하면 true, 일치하지 않으면 false
};
// 쿼리에 맞는 미디어 쿼리 결과를 가져옴
const result = matchMedia(query);
result.addEventListener("change", onChange); // 화면 크기가 변할 때마다 onChange 실행
setValue(result.matches); // 초기 상태 설정
return () => result.removeEventListener("change", onChange); // cleanup
}, [query]);
return value; // 쿼리 결과 (true/false) 반환
};
코드 설명
useState(false)
: 초기 값은 false로 설정. 이후 미디어 쿼리 조건에 따라 true 또는 false로 변경됩니다.matchMedia(query)
: CSS의 미디어 쿼리처럼 조건을 설정하고, 조건에 맞는지 확인합니다. 여기서 query는 "(max-width: 768px)"와 같은 값을 받습니다.event.matches
: 미디어 쿼리 조건이 일치하면 true, 일치하지 않으면 false로 업데이트됩니다.addEventListener / removeEventListener
: 화면 크기가 변경될 때 미디어 쿼리 조건을 다시 확인합니다.
MediaQueryProvider로 상태 제공
이제 미디어 쿼리로 감지한 값을 앱 전체에서 사용할 수 있도록 컨텍스트로 제공하겠습니다. 이를 통해 모바일 여부를 어디서든 쉽게 확인할 수 있습니다.
// MediaQueryContext.tsx
import React, { createContext, ReactNode } from "react";
import { useMediaQuery } from "@/hook/useMediaQuery";
export const MediaQueryContext = createContext<boolean | null>(null);
export const MediaQueryProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
const isMobile = useMediaQuery("(max-width: 768px)");
return <MediaQueryContext.Provider value={isMobile}>{children}</MediaQueryContext.Provider>;
};
코드 설명
MediaQueryContext
: 모바일 여부(true/false)를 저장하는 컨텍스트를 생성합니다.MediaQueryProvider
: useMediaQuery 훅을 사용해 모바일 감지 상태를 생성하고, 이를 자식 컴포넌트들에게 제공합니다.(max-width: 768px)
: 모바일 화면을 768px 이하로 정의해, 해당 크기 이하에서는 모바일로 인식하도록 설정합니다.
Root
컴포넌트에서 MediaQueryProvider로 감싸기
이제 이 컨텍스트를 Root
컴포넌트에 적용해 앱 전체에서 모바일 여부를 사용할 수 있도록 설정합니다.
// root.tsx
import { Outlet } from "react-router-dom";
import { MediaQueryProvider } from "@/context/MediaQueryContext";
export const Root = () => {
return (
<MediaQueryProvider>
<div className="flex justify-center">
<div className="w-full max-w-max flex flex-col min-h-screen">
<div className="flex-grow">
<Outlet />
</div>
</div>
</div>
</MediaQueryProvider>
);
};
코드 설명
MediaQueryProvider
: 모바일 여부를 전역적으로 제공하여, 어느 컴포넌트에서든 모바일 감지를 쉽게 할 수 있습니다.Outlet
: React Router를 통해 동적으로 하위 컴포넌트를 렌더링하는 역할을 합니다.
실제 사용 화면
다음은 이 Custom 훅을 사용하여 렌더링 된 화면 캡쳐본입니다.
위 사진에 나온 것처럼 데스크톱일 때에는 display 속성을 flex
, 모바일일 때에는 flex-col
로 변경하는 등의 필요한 작업을 수행할 수 있습니다.
깃헙 소스코드 공유
아마 여기까지 글을 읽으신 분들은 현재 진행하고 있는 사이드/토이 프로젝트에 적용하고자 하실 텐데, 위 내용만으로는 제대로 작동이 안 할 가능성이 큽니다.
제가 사용한 Vite 빌드 도구
, compileoption
등이 달라서 그럴 겁니다.
그래서 모바일 반응형 웹 개발 템플릿을 만들어 깃헙에 공유하겠습니다.
README.mdv
에 가이드라인도 작성했으니 사용하실 분들은 참고하시면 됩니다.
https://github.com/itsjh1242/react-responsive-template
'코드 > React' 카테고리의 다른 글
class를 안쓰고 className을 쓰는 이유 (0) | 2024.06.20 |
---|---|
생명주기(Lifecycle) (0) | 2024.06.20 |
Virtual Dom이란? (0) | 2024.06.20 |
React에서 Audio 사용하기 (0) | 2024.02.28 |