고윤태의 개발 블로그

누구나 접근하기 쉽지만 얻어 가는 것이 있는 글을 쓰기 위해 노력 중입니다.

개발

React useDeviceType 사용자 환경을 반환하는 hook

고윤태 2023. 6. 11. 17:43

개요

안녕하세요 이번에 작성하게 될 내용은 제가 작성한 useDeviceType이라는 custom hook에 대해 글을 작성해 보겠습니다.


개발 환경

React + TypeScript로 진행되었습니다.


구현하게 된 계기

이번에 React를 사용하여 Web 환경에서 토이 프로젝트를 하나 진행하고 있습니다.

이런 식의 UI를 구현하게 되었습니다.
개발하는 도중 문득 모바일 우선 디자인 (Mobile First Design)이라는 것이 떠올랐습니다.
만약 제가 이것을 떠올리지 않았더라면 이 useDeviceType라는 custom hook을 개발하지 않았을 수도 아니면 시간이 더 흐르고 개발했을 수도 있습니다.

저는 생각이 난 김에 이 UI에서 어떻게 하면 모바일 사용자에게 편의성을 제공하는 UI를 구성할까 고민을 하기 시작했고
모바일에게 친숙한 애플리케이션들을 확인을 해보니 BottomNavigation으로 구성이 되어 있는 경우가 대다수였기에 저도 그것을 적용하기로 마음먹었습니다.

bottomNavigation 예시

그 후 간단하게 디자인을 해보았습니다.

아래 사진이 간단하게 디자인을 한 결과입니다.

Mobile 예시

만약 제가 모바일 우선 디자인을 떠오르지 않았더라면 상위 Header에 모바일에서는 햄버거 메뉴를 만들었을 거 같습니다.
그렇게 메뉴가 숨겨져있다면 사용자는 이 서비스에 어떤 기능들이 있는지 파악하기 어려울 것입니다. 주요 기능들이 많지는 않지만 BottomNavigation을 활용하여 주요 기능들을 바로 노출시켜줌으로써 사용자가 서비스에 대해서 학습하는 리소스를 최소화시킬 수 있습니다.

특히, 헤더에 너무 많은 항목들을 넣으려면 디자인적으로도 매우 복잡했을 것 같습니다.

 

이러한 이유로 결정을 내린 끝에 Web과 Mobile 환경에서 렌더링 시켜야 하는 컴포넌트의 차이가 생겼습니다.
이 상황을 해결하기 위해 사용자의 환경을 확인하는 useDeviceType을 만들게 됐습니다.


구현

import { useState, useEffect } from 'react';

const useDeviceType = () => {
  const [deviceType, setDeviceType] = useState('');

  useEffect(() => {
    const handleResize = () => {
      const isMobile = window.innerWidth <= 768;
      const isTouchDevice =
        'ontouchstart' in window || navigator.maxTouchPoints > 0;
      const isMobileDevice =
        /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
          navigator.userAgent
        );

      const type =
        isMobile || isTouchDevice || isMobileDevice ? 'Mobile' : 'Web';
      setDeviceType(type);
    };

    handleResize();

    window.addEventListener('resize', handleResize);
    return () => {
      window.removeEventListener('resize', handleResize);
    };
  }, []);

  return deviceType;
};

export default useDeviceType;

코드 설명

  • isMobile : 현재 창의 widht 사이즈가 760보다 작거나 같은지 boolean으로 반환합니다.
  • isTouchDevice : 터치 이벤트를 지원하는 디바이스인지 booelan으로 반환합니다.
  • isMobileDevice : 일부 모바일 디바이스인지 booelan으로 반환합니다.

창 크기 변경 시에도 디바이스 타입을 window resize event를 통하여 실시간으로 업데이트합니다. 리스너를 제거하여 메모리 누수를 방지하기 위해 컴포넌트가 언마운트될 때 리스너를 정리합니다.

 

이렇게 다양한 속성을 비교함으로써 더 정확한 디바이스 감지를 수행할 수 있다고 생각합니다.

 

그러나 이러한 방법도 100% 정확하지는 않으며, 환경에 따라 일부 예외가 있을 수 있습니다.


활용 예시

  const deviceType = useDeviceType();

  return (
      <BrowserRouter>
        <Header />
        <div className="flex">
          {deviceType === 'Web' && <SideBar />}
          <section className="w-full">
            <Routes>
           		//...page components
            </Routes>
          </section>
        </div>
        {deviceType === 'Web' ? <Footer /> : <BottomNavigation />}
      </BrowserRouter>
  );

후기

그동안 업무에서 작업했던 것이 WebView 내의 Web을 개발 또는 Web에서만 사용하는 백오피스 웹이었기에
Mobile과 Web을 둘 다 고려하는 것에서 미숙했습니다.
이번 계기로 좀 더 모바일 UI/UX를 고려하는 개발자로 성장하기 위해 이제 막 한 걸음 시작하는 좋은 기회가 된 거 같습니다.