본문 바로가기

프론트엔드/React

react-window로 렌더링 성능 최적화하기

  • 프로젝트: 채식 지도
  • 키워드: react-window, rendering optimization
  • 상황
    1. 매장 목록을 보여주는 UI를 구현하였는데, 매장 Row마다 각각 'button' DOM으로 구현되어있다.
      매장 UI
    2. Next.js 프레임워크 덕분에 이미지(<Image />)는 자동으로 lazy loading 되고 있어서 image 최적화 문제는 없을 것이다.
    3. 하지만 훗날 매장이 점점 많아지고 리스트가 길어짐에 따라 DOM 렌더링 성능에 문제가 생기고 화면이 끊겨보일 가능성이 있다. 그래서 사용자에게 보이는 DOM만 실제로 렌더링하는 'windowing 기법'을 써보기로 한다.
  • 해결 방법
    1. react 라이브러리 중 react-window라는 라이브러리를 설치한다.
        yarn add react-window
      yarn add -D @types/react-window


    2. 기존에는 StoreList라는 이름의 'div' 태그 하위에 각 매장 Row(button)를 렌더링했다.
        // StoreList.tsx
      import styled from '@emotion/styled';
      import Image from 'next/image';
      
      <StoreList height={...}>
        {stores.map((poi) => (
          <button onClick={...} key={poi.id}>
            <Image src={poi.image} width={80} height={80} objectFit="cover" alt="" />
            ...매장 정보...
          </div>
        </button>
      ))}
      </StoreList>
      
      const StoreList = styled.div<{ height: number }>`
        overflow: scroll;
        height: calc(${({ height }) => ...}px);
      `;


    3. 이제 StoreList div 태그를 react-window에서 지원하는 List 컴포넌트로 교체하면 된다.
      각 매장 Row의 높이(height)가 일정한 경우이기 때문에,  react-window의 'FixedSizeList'를 사용한다.
        // StoreList.tsx
      import Image from 'next/image';
      import { FixedSizeList } from 'react-window';
      
      <FixedSizeList height={...} itemCount={stores.length} itemSize={113} width="100%">
        {({ style, index }) => {
          const poi = stores[index];
          return (
            <button style={style} onClick={...} key={poi.id}>
              <Image src={poi.image} width={80} height={80} objectFit="cover" alt="" />
              ...매장 정보...
            </div>
          </button>
          );
        }};
      </FixedSizeList>


    4. 적용 후 개발자 도구를 통해 DOM 렌더링이 실제로 적게 되는지 확인한다.
      react-window를 적용한 목록 UI
      button style에 추가된 position, left, top 속성으로 알 수 있듯이 windowing 기법은 각 Row의 절대 위치를 조절하여 실제로 스크롤 되는 듯한 착시를 준다.
      스크롤시 DOM 변화
      기존과 달리 이제는 사용자에게 보이는 영역 근처 8개의 button만 렌더링된다!