직접 DOM을 조작해 불필요한 리렌더링 없애기

4차 프로젝트 트러블슈팅
트러블슈팅 프로젝트
avatar
2025.03.12
·
6 min read

4차 프로젝트를 진행하면서 발생했던 트러블 슈팅에대해서 말해보겠습니다.

3D 스와이퍼 이미지를 호버해서 x,y좌표값이 바뀔때마다 발생하는 리렌더링

3979

현재 Room안에서 CD랙이 있는 위치로 사용자가 이동했을때 보여지는 상황이다.

그렇기 때문에 단순한 스와이퍼로 CD 앨범 이미지를 보여주는 것은 UI적으로 만족스럽지 않았고 스와이퍼에서 제공하는 swiper coverflow를 사용하여 3D 효과를 주었다.

또한 단순히 3D로 보여주는 것보다는 호버해서 마우스를 움직였을때 이리저리 앨범 이미지가 움직일 수 있는 효과를 내기위해 mouseMove 이벤트가 발생할때마다 앨범에 회전값을 주도록 하였다.

코드

  const handleMouseMove = (
      e: React.MouseEvent<HTMLDivElement, MouseEvent>,
      index: number,
    ) => {
      const rect = e.currentTarget.getBoundingClientRect(); // 요소 위치 정보 가져오기
      const x = e.clientX - rect.left; // 요소 내부에서의 X 좌표
      const y = e.clientY - rect.top; // 요소 내부에서의 Y 좌표

      const rotateX = (1 / 10) * y - 20;
      const rotateY = (1 / -10) * x + 20;

      setRotateX((prev) => ({
        ...prev,
        [index]: rotateX, // 마우스를 움직일 때 X축 회전값 적용
      }));
      setRotateY((prev) => ({
        ...prev,
        [index]: rotateY, // 마우스를 움직일 때 Y축 회전값 적용
      }));
    };

    const handleMouseLeave = (index: number) => {
      setRotateX((prev) => ({
        ...prev,
        [index]: 0, // 마우스를 떼면 원래대로 복귀
      }));
      setRotateY((prev) => ({
        ...prev,
        [index]: 0, // 마우스를 떼면 원래대로 복귀
      }));
    };

마우스가 앨범안에서 움직일때, 떠날때 호출할 함수들

🚨 문제 배경

현재 x,y좌표값이 바뀔때마다 새로운 x,y축 회전값이업데이트 되는 방식으로 구현하여 살짝만 마우스를 움직여도 해당 컴포넌트가 리렌더링 되는 문제가 발생했다.

🔥 시도했던 방법

x,y값이 바뀔 때 마다 상태값이 변화되어 리렌더링이 되는 것이기 때문에 x,y 좌표값의 변화를 상태값으로 관리할 수 없었다. 따라서 직접 DOM을 조작하는 방식으로 수정해보았다.

바뀐 코드

 const rotateRefs = useRef<{ [key: number]: { x: number; y: number } }>({});

    useEffect(() => {
      const handleMouseMove = (
        e: React.MouseEvent<HTMLDivElement>,
        index: number,
      ) => {
        const rect = e.currentTarget.getBoundingClientRect();
        const x = e.clientX - rect.left;
        const y = e.clientY - rect.top;

        const rotateXValue = (1 / 10) * y - 20;
        const rotateYValue = (1 / -10) * x + 20;

        rotateRefs.current[index] = { x: rotateXValue, y: rotateYValue };

        const element = e.currentTarget;
        element.style.transform = `rotateY(${rotateYValue}deg) rotateX(${rotateXValue}deg)`;
        element.style.filter = `drop-shadow(${rotateYValue}px ${rotateXValue}px 20px rgba(0, 0, 0, 0.2))`;
      };


      const handleMouseLeave = (index: number) => {
        rotateRefs.current[index] = { x: 0, y: 0 };

        const element = document.getElementById(`cdSlide-${index}`);
        element.style.transform = `rotateY(0deg) rotateX(0deg)`;
        element.style.filter = 'drop-shadow(0px 0px 20px rgba(0, 0, 0, 0.2))';
      };

      cdRackDatas.forEach((_, index) => {
        const slide = document.getElementById(`cdSlide-${index}`);
        if (slide) {
          slide.addEventListener('mousemove', (e) => handleMouseMove(e, index));
          slide.addEventListener('mouseleave', () => handleMouseLeave(index));
        }
      });

      return () => {
        cdRackDatas.forEach((_, index) => {
          const slide = document.getElementById(`cdSlide-${index}`);
          if (slide) {
            slide.removeEventListener('mousemove', (e) =>
              handleMouseMove(e, index),
            );
            slide.removeEventListener('mouseleave', () =>
              handleMouseLeave(index),
            );
          }
        });
      };
    }, [cdRackDatas]);

각각의 3D 스와이퍼 이미지에 ref를 걸어놓은 후 mouse가 이미지 내에서 움직일때 DOM을 직접 조작하여 x,y 좌표에 따른 rotate의 값을 지정해주는 방식

👀 이전 코드와 비교

이전 코드는 3D 스와이퍼 이미지를 호버할때마다 리렌더링이 발생했습니다.

3980

하지만 x,y값의 변화를 useState로 받아 상태변화를 처리하지않고 DOM에 직접접근하여 rotate값을 수정해주는 방식을 사용했습니다.

3981

기존에는 Hover 시 마다 발생하는 mousemove 이벤트로 인해 불필요한 리렌더링이 100회 발생하였으나,   React 상태 대신 DOM 직접 조작을 활용하여 리렌더링을 1회로 감소시켜 렌더링 횟수를 99% 최적화하였습니다.   이를 통해 성능을 개선하고, 렌더링 비용을 최소화하였습니다.

🤩 알게된 점

React 상태 관리 없이 DOM을 직접 조작하면 불필요한 리렌더링을 줄일 수 있었습니다.

이번 경험을 통해 렌더링 최적화의 중요성과, DOM 직접 조작이 필요한 상황을 명확히 이해할 수 있었습니다.







- 컬렉션 아티클