브라우저 resize 성능 개선 작업
브라우저 창 크기 변경 시 버벅거리는 현상 해결하기
작성일 2024.07.17
페이지가 생성된 시간 2026.02.03 00:47:09

1

문제인식

브라우저 창 크기 변경 시 버벅거리는 현상 100% 발생

개발하는 서비스 특성상 한 페이지에 기능이 매우 많고 무겁기 때문에 window나 document resize 이벤트 수신하는 부분이 무분별하게 많아졌고, 성능문제로 나타났다.

작업 내용 요약

viewport 너비값을 사용하는 컴포넌트들의 성능을 개선

  1. useWindowSize 훅을 사용하는 컴포넌트들
  • useWindowSize훅을 가져다 쓰는 컴포넌트의 수만큼 이벤트리스너가 등록되는 점
  • 1픽셀 단위로 리렌더링이 발생하는 점
  1. useMediaQuery 훅을 사용하는 컴포넌트들
  • SSR 시 hydration mismatch 문제가 발생할 수 있는 점
    • 라이브러리 문서에서는 제공되는 Context를 이용해 width값을 지정해줄 수 있다고는 하지만 Context를 관리해야 하는 불편함이 존재
  • SSR 시 UI 불일치가 발생하는 문제 (hydration mismatch 문제가 발생했을 때인 것으로 추측)
    • 여러 이슈들을 진행하면서 여러번 겼었고, 임시방편으로 useIsClient 훅을 이용해 해결했었다.

useViewportWidth

react 18 버전에 등장한 useSyncExternalStore 훅을 이용해 window.innerWidth 값에 반응하는 훅을 만들었습니다.

useSyncExternalStore 훅은 브라우저 API 구독 시에 사용해볼 수 있다고 react 공식문서에 소개되어 있다.

관련 공식문서, useSyncExternalStore 훅을 이용해 scrollY 값을 구독하는 예시

// function useViewportWidth<T extends Primitive>(selector: (width: number) => T): T | null
const isMobile = useViewportWidth((width) => width <= 767);

전체코드

// subscribeViewportSize.ts
const subscribers = new Set<VoidFunction>();

const handleResize = () => {
  subscribers.forEach((callback) => callback());
};

export const subscribeViewportSize = (callback: VoidFunction) => {
  if (subscribers.size === 0) {
    window.addEventListener('resize', handleResize);
  }
  subscribers.add(callback);

  return () => {
    subscribers.delete(callback);
    if (subscribers.size === 0) {
      window.removeEventListener('resize', handleResize);
    }
  };
};

// useViewportWidth.ts
/**
 * window.innerWidth를 구독하는 훅
 * 서버사이드에서는 null을 반환
 * @param selector (필수) window.innerWidth를 이용해 값을 계산하는 함수, 리턴하는 값에 따라 리렌더링 여부 결정
 */
export const useViewportWidth = <T extends Primitive>(selector: (width: number) => T) => {
  return useSyncExternalStore<T | null>(
    subscribeViewportSize,
    () => selector(window.innerWidth),
    () => null
  );
};

개선사항

  • window에 resize 이벤트리스너가 1개만 달리도록 개선
  • selector를 이용해 리렌더링 개선, selector가 반환하는 값을 기준으로 변경을 감지
  • SSR 사용에도 문제가 없으며 Server Side에서는 null을 반환해 훅을 사용하는 컴포넌트에서 별도로 처리가 가능

회고

작업후 모든 브라우저 너비, 높이(useViewportHeight 도 추가구현)관련 로직들을 내가 작성한 훅으로 전부 마이그레이션하고 성능비교를 통해 유의미한 결과를 얻을 수 있었다. 오랜만에 리액트 공식문서를 뒤적이다가 인사이트를 얻어 퇴근하고 작업했었다. 짧은 코드가 프로덕트에 큰 개선을 줄 수 있음을 다시 느낀다.

AS-IS

TO-BE

©2024 dlwl98
github
PostsAbout