브라우저 resize 성능 개선 작업브라우저 창 크기 변경 시 버벅거리는 현상 해결하기작성일 2024.07.17페이지가 생성된 시간 2026.02.03 00:47:09
1
문제인식
브라우저 창 크기 변경 시 버벅거리는 현상 100% 발생
개발하는 서비스 특성상 한 페이지에 기능이 매우 많고 무겁기 때문에 window나 document resize 이벤트 수신하는 부분이 무분별하게 많아졌고, 성능문제로 나타났다.
작업 내용 요약
viewport 너비값을 사용하는 컴포넌트들의 성능을 개선
useWindowSize훅을 사용하는 컴포넌트들
useWindowSize훅을 가져다 쓰는 컴포넌트의 수만큼 이벤트리스너가 등록되는 점- 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
