화면 크기 전역 상태로 관리

Viking_J·2024년 9월 22일
post-thumbnail

문제

우주윗미 프로젝트 리팩토링 했어요.

문제:
화면을 조금만 바꿔도 handleResize가 엄청 많이 돌아간다.

상황

// 커스텀 훅 제작
"use client";

import { useEffect, useState } from "react";

const useIsMobile = (): boolean => {
  const [isMobile, setIsMobile] = useState(false);

  useEffect(() => {
    const handleResize = () => {
      setIsMobile(window.innerWidth <= 480);
    };

    handleResize();

    window.addEventListener("resize", handleResize);

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

  return isMobile;
};

export default useIsMobile;

상황1: 화면 크기가 모바일인지 아닌지에 따라서 조건부 렌더링이 필요.
상황2: 화면 크기를 검사하는 useIsMobile 훅을 만듬.
상황3: 조건부 렌더링이 필요한 컴포넌트가 여러 곳에 있다.
상황4: 여러 곳에서 useIsMobile 훅을 사용 중.


문제 원인

원인1:
resize는 화면 크기가 바뀔 때 마다 코드가 돌아간다.

원인2:
useIsMobile를 여러 곳에서 사용했기 때문에 동일한 로직의 이벤트 리스너가 여러 번 등록된다.

원인1 + 원인2:
동일한 이벤트 핸들러 여러 개가 화면 크기가 바뀔 때 마다 돌아간다.


해결

해결1:

isMobile 변수를 zotai를 활용해 전역 상태로 만든다.

//전역 상태  생성
import { atom } from "jotai";

/** 모바일 화면인지 아닌지 */
const isMobileAtom = atom<boolean>(false);

export default isMobileAtom;

=> 화면 크기는 어떤 컴포넌트에서나 똑같다. 그러므로 동일한 값을 가진 상태를 여러개 만들 이유가 없다. 예를 들어 다크모드이거나 언어가 영어로 설정 된다면 모든 컴포넌트에서 다크이고 영어다. 그래서 이런 경우 전역상태로 관리하곤 한다. 화면 크기도 마찬가지다.

해결2:

화면 크기가 필요한 곳에서는 전역 상태 값을 이용하도록 한다.

// 훅 로직 변경
"use client";

import { useAtom } from "jotai";

import { isMobileAtom } from "@/stores";

const useIsMobile = (): boolean => {
  const [isMobile] = useAtom(isMobileAtom);

  return isMobile;
};

export default useIsMobile;

=> 이벤트 리스너를 등록하고 빼는 로직은 전부 삭제. 여러 곳에서 동일한 이벤트 리스너를 등록할 필요가 없다.

해결3:

화면 크기를 검사하는 로직을 가진 컴포넌트 하나를 제작해서 최상위에 넣는다.

// example
export default function RootLayout({
  children,
}:{
  children: React.ReactNode;
}) {
  return (
    <html lang="ko">
      <body>
        <MobileSizeWatcher /> // 이 친구!
    	<Header />
        {children}
      </body>
    </html>
  );
}

=> Next.js & app router를 사용 중이기 때문에 최상단 레이아웃에 컴포넌트를 넣어주었다. 저 컴포넌트 안에서만 화면 크기를 검사하고 isMobile 전역상태를 업데이트 해준다.

해결4:

resize 대신 matchMedia 사용.

"use client";

import { useAtom } from "jotai";
import { useEffect } from "react";

import { isMobileAtom } from "@/stores";

export default function MobileSizeWatcher() {
  const [, setIsMobile] = useAtom(isMobileAtom);
  useEffect(() => {
    const mediaQuery = window.matchMedia("(max-width: 480px)");

    const handleMediaQueryChange = (e: MediaQueryListEvent) => {
      setIsMobile(e.matches);
    };

    // 초기 렌더링 시 확인
    setIsMobile(mediaQuery.matches);

    // 미디어 쿼리 변경 시 이벤트 등록
    mediaQuery.addEventListener("change", handleMediaQueryChange);

    return () => {
      mediaQuery.removeEventListener("change", handleMediaQueryChange);
    };
  }, [setIsMobile]);

  return null;
}

=> matchMedia는 미디어 쿼리가 일치하는지 검사하는 메소드. 이 메소드를 사용해서 모바일 사이즈보다 작아졌을 때만 함수가 돌아가도록 했다.


결과

결과1:
모바일인지 아닌지 정보를 가진 변수를 전역 변수 하나로만 관리 됨.

결과2:
이벤트 리스너가 하나만 등록됨.

결과3:
화면 크기가 모바일로 줄어들 때 한번만 함수가 돈다.

profile
모험을 떠나보자

0개의 댓글