코드/React

[React] 반응형 웹개발 가이드라인(Custom Hook)

juundev 2024. 10. 14. 11:51

반응형 웹과 모바일 감지의 중요성

웹 개발에서 반응형 웹 디자인은 이제 필수입니다. 데스크톱, 태블릿, 모바일 등 다양한 디바이스에 맞춘 최적화된 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

 

GitHub - itsjh1242/react-responsive-template: 🚀 A responsive React template built with Vite for fast and efficient developmen

🚀 A responsive React template built with Vite for fast and efficient development. 📱 Includes a custom hook for detecting mobile devices using media queries, allowing you to easily create responsive...

github.com

 

'코드 > React' 카테고리의 다른 글

class를 안쓰고 className을 쓰는 이유  (0) 2024.06.20
생명주기(Lifecycle)  (0) 2024.06.20
Virtual Dom이란?  (0) 2024.06.20
React에서 Audio 사용하기  (0) 2024.02.28