본문 바로가기
JavaScript/React

React) 컴포넌트 성능 최적화 - react-virtualized (렌더링 최적화)

by 박채니 2022. 11. 21.
안녕하세요, 코린이의 코딩 학습기 채니 입니다.
[리액트를 다루는 기술]의 책을 참고하여 포스팅한 개인 공부 내용입니다.

 

컴포넌트 성능 최적화

 

react-virtualized를 사용한 렌더링 최적화

기존에 만든 일정 관리 웹 애플리케이션에 초기 데이터가 2,500개 등록되어 있습니다.

하지만, 실제 화면에 나오는 일정은 단 9개뿐이고 나머지는 스크롤을 해야만 볼 수 있습니다.

 

현재 컴포넌트가 처음 렌더링될 때 2,500개 컴포넌트 중 2,491개 컴포넌트는 스크롤하기 전에 보이지 않음에도 불구하고 렌더링이 이루어집니다.

즉, 시스템 자원 낭비라고 볼 수 있습니다.

 

react-virtualized를 사용하면 스크롤되기 전에 보이지 않는 컴포넌트는 렌더링하지 않고 크기만 차지하게끔 해주고,

스크롤되면 해당 스크롤 위치에 보여 줘야하는 컴포넌트를 렌더링 시켜줍니다.

 

최적화 준비

 

$ yarn add react-virtualized

react-virtualized를 설치한 후, 제공해주는 List 컴포넌트를 이용하여 TodoList 컴포넌트를 최적화 시켜보겠습니다.

 

먼저, 각 항목의 실제 크기를 px단위로 알아내야 합니다.

크롬 개발자 도구에서 좌측 상단에 있는 아이콘을 클릭 후 크기를 알고 싶은 항목에 커서를 대보면 알 수 있습니다.

 

가로 493.47px 세로 56.98px 인 것을 확인할 수 있습니다.

이 때 첫 번째 항목이 아닌 두 번째 항목의 크기를 확인하였는데, 첫 번째 항목은 테두기라 포함되어있지 않기 때문입니다.

(스크롤바의 가로 크기도 포함시켜야 하므로 최종적으론 511.99 x 56.98이 됨)

 

TodoList 수정

 

TodoList.js

import React, { useCallback } from 'react';
import { List } from 'react-virtualized';
import './TodoList.scss';
import TodoListItem from './TodoListItem';

const TodoList = ({ todos, onRemove, onToggle }) => {
    const rowRenderer = useCallback(({ index, key, style }) => {
        const todo = todos[index];
        return (
            <TodoListItem todo={todo} key={key} onRemove={onRemove} onToggle={onToggle} style={style} />
        )
    }, [onRemove, onToggle, todos]);

    return (
        <List 
            className='TodoList'
            width={511.99} // 전체 크기
            height={512.82} // 전체 높이
            rowCount={todos.length} // 항목 개수
            rowHeight={56.98} // 항목 높이
            rowRenderer={rowRenderer} // 항목을 렌더링할 때 쓰는 함수
            list={todos} // 배열
            style={{ outline: 'none' }} // List에 기본 적용되는 outline 스타일 제거
        />
    )
}

export default React.memo(TodoList);

List 컴포넌트 사용을 위해 rowRenderer 함수를 작성하였습니다.

해당 함수는 react-virtualized의 List 컴포넌트에서 각 TodoItem을 렌더링할 때 사용하고, List 컴포넌트의 props로 설정해줍니다. (파라미터에 index, key, style 값을 객체로 받아와 사용)

 

List 컴포넌트 사용 시,

리스트의 전체 크기, 각 항목 높이, 항목 렌더링 함수, 배열을 props로 넣어줍니다.

그 후 전달 받은 props를 사용해 자동으로 최적화 시켜줍니다.

 

TodoListItem 수정

 

TodoList를 저장하니 스타일이 깨져서 나타났습니다. 이를 바로 잡기 위해 TodoListItem 컴포넌트를 수정하겠습니다.

 

TodoListItem.js

import React from 'react';
import cn from 'classnames'
import { MdCheckBox, MdRemoveCircleOutline, MdCheckBoxOutlineBlank } from 'react-icons/md';
import './TodoListItem.scss';

const TodoListItem = ({ todo, onRemove, onToggle, style }) => {
    const { id, text, checked } = todo;

    return (
        <div className='TodoListItem-virtualized' style={style}>
            <div className='TodoListItem'>
                <div className={cn('checkbox', { checked })} onClick={() => onToggle(id)} >
                    {checked ? <MdCheckBox /> : <MdCheckBoxOutlineBlank />}
                    <div className='text'>{text}</div>
                </div>
                <div className='remove' onClick={() => onRemove(id)}>
                    <MdRemoveCircleOutline />
                </div>
            </div>
        </div>
    );
}

export default React.memo(TodoListItem);

TodoListItem-virtualized 클래스 값을 가진 <div> 태그를 생성하고 props로 받은 style을 적용시켜주었습니다.

해당 클래스 생성은 컴포넌트 사이에 테두리를 제대로 쳐주고, 짝수 번째 항목에 다른 배경 색상을 설정하기 위해서 입니다.

 

TodoListItem.scss

.TodoListItem-virtualized {
    // 엘리먼트 사이사이에 테두리
    & + & {
        border-top: 1px solid #dee2e6;
    }
    &:nth-child(even) { // 짝수 자식 요소
        background: #f8f9fa;
    }
}

...
...

기존 스타일 파일에서 & + &와, &:nth-child(even) 코드를 없애고, .TodoListItem-virtualized에 해당 코드들을 위치시켜주었습니다.

 

작업을 완료 후 성능 측정을 해보니 무려 3.9ms가 나온 것을 확인할 수 있습니다.

React.memo를 통해 15.6ms까지 줄였는데 react-virtualized를 이용하니 3.9ms까지 줄어든 것이죠.