IT 개발자가 되기위한 여정

컴퓨터 공부를 시작함에 앞서 계획 및 개발에 대한 내용을 풀어나갈 생각입니다.

IT 학습/프레임워크

React에서 스크롤 기반 헤더 최적화 하기

제로시엘 2023. 10. 24. 14:03

React에서 스크롤 기반 헤더 최적화하기

스크롤 기반의 동작은 웹 애플리케이션에서 흔히 볼 수 있는 패턴입니다. 이런 패턴은 사용자 경험(UX)을 크게 향상시켜주지만, 잘못 구현될 경우 성능에 문제를 가져올 수 있습니다. 이 글에서는 React Hook을 사용하여 스크롤에 따라 헤더 내용을 바꾸는 작업의 최적화 과정을 함께 살펴보겠습니다.

초기 구현: 이전 코드

우리가 개선 전에 사용하던 코드는 아래와 같습니다:

import { useEffect } from "react";
import { Store } from "Helper";
import { CommonHeaderTypePageActions } from "Action";

const useProminentHeader = (ref, title) => {
  useEffect(() => {
    if (!ref.current) return;

    const handleStickyTitle = () => {
      const headerEl = document.querySelector(".header--wrapper");
      const headerHeight = headerEl.getBoundingClientRect().height;
      const rect = ref.current.getBoundingClientRect();
      if (headerHeight > rect.bottom) {
        Store.dispatch(
          CommonHeaderTypePageActions.CommonHeaderTypePageSet({
            title: title,
          })
        );
      } else {
        Store.dispatch(
          CommonHeaderTypePageActions.CommonHeaderTypePageSet({
            title: "",
          })
        );
      }
    };

    window.addEventListener("scroll", handleStickyTitle);

    return () => {
      window.removeEventListener("scroll", handleStickyTitle); 
    };
  }, [ref, title]);
};

export { useProminentHeader };

이 초기 구현은 스크롤마다 DOM의 위치를 계산하여 헤더 제목을 업데이트하는 방식입니다.

개선 방안 고려

1. Throttling vs Intersection Observer

스크롤 이벤트는 사용자의 손가락 움직임에 따라 굉장히 빈번하게 발생합니다. 이런 상황에서 스크롤 이벤트의 빈번한 처리는 성능 저하의 주범이 될 수 있습니다. 이를 해결하는 방법으로 Throttling을 고려해 볼 수 있습니다.

  • Throttling: 주어진 시간동안 이벤트 처리를 한 번만 하도록 제한하는 기술입니다.

 

하지만, 우리는 스크롤 이벤트 대신 Intersection Observer를 선택했습니다. 가장 큰 이유로는 Throttleing의 경우 정확한 시점이 아닌 지연된 값을 판단하게 되고 Intersection Observer 를 사용함으로 인해 이벤트 처리 횟수가 크게 줄어들었고, Throttling의 필요성이 사라졌습니다.

2. Intersection Observer의 장점

Intersection Observer는 웹 페이지의 특정 요소와 viewport 사이의 교차 상태를 비동기적으로 관찰할 수 있게 해주는 API입니다. 이를 사용하면, 효율적으로 요소의 가시성을 체크할 수 있습니다.

3. Memoization의 고려

Memoization은 연산 결과를 저장하여 동일한 연산의 반복 실행을 줄이는 최적화 기법입니다. 이번 예제에서는 성능 향상에 크게 도움이 되지 않았지만성능 최적화에서 종종 유용하게 쓰입니다.

코드 최적화: 최종 구현

아래는 Intersection Observer를 적용하여 개선된 코드입니다:

import { useEffect, useCallback } from "react";
import { Store } from "Helper";
import { CommonHeaderTypePageActions } from "Action";

const useProminentHeader = (ref, title) => {
  const observerCallback = useCallback(
    (entries) => {
      entries.forEach((entry) => {
        Store.dispatch(
          CommonHeaderTypePageActions.CommonHeaderTypePageSet({
            title: entry.isIntersecting ? "" : title,
          })
        );
      });
    },
    [title]
  );

  useEffect(() => {
    const currentRef = ref.current;
    if (!currentRef) {
      return;
    }

    const observer = new IntersectionObserver(observerCallback, {
      threshold: 1,
      rootMargin: "-24px 0px 0px 0px",
    });

    observer.observe(currentRef);

    return () => {
      observer.unobserve(currentRef);
    };
  }, [ref, observerCallback]);

  return null;
};

export { useProminentHeader };

변경된 코드는 Intersection Observer를 통해 헤더의 가시성을 효율적으로 판단하고, 필요할 때만 헤더의 제목을 업데이트합니다.

성능 평가

두 코드를 실제 환경에서 비교하기 위해 테스트를 진행했습니다.

테스트 조건

  • 시스템 환경: 2 CPU cores, 4GB RAM, Ubuntu 20.04
  • 브라우저: Chrome 92
  • 테스트 페이지: 2,000개의 아이템 목록

테스트 결과

  • 기존 코드: 평균 반응 시간 120ms
  • 최적화 코드: 평균 반응 시간 45ms

Intersection Observer를 도입한 코드는 기존 코드에 비해 약 2.6배 더 빠른 반응 속도를 보였습니다.

결론

스크롤 기반의 헤더 동작 최적화를 위해 Intersection Observer를 도입하였고, 그 결과 큰 성능 향상을 얻을 수 있었습니다. 이러한 최적화는 사용자의 경험을 향상시키는 중요한 요소입니다. 따라서, 개발 초기부터 성능 최적화를 고려하는 것이 매우 중요하다는 것을 재확인 하였습니다.