안녕하세요, 코린이의 코딩 학습기 채니 입니다.
[리액트를 다루는 기술]의 책을 참고하여 포스팅한 개인 공부 내용입니다.
컴포넌트 성능 최적화
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까지 줄어든 것이죠.
'JavaScript > React' 카테고리의 다른 글
React) 리액트 라우터로 SPA 개발하기 - 라우팅이란, SPA/MPA의 차이, Route 컴포넌트, Link 컴포넌트 (2) | 2022.11.22 |
---|---|
React) immer - 사용법 및 useState 함수형 업데이트와 immer 함께 쓰기 (0) | 2022.11.21 |
React) 컴포넌트 성능 최적화 - React.memo, useState의 함수형 업데이트, useReducer, 불변성의 중요성(얕은 복사) (0) | 2022.11.20 |
React) 컴포넌트 성능 최적화 - React DevTools, 느려지는 원인 분석 (2) | 2022.11.20 |
React) 일정 관리 웹 애플리케이션 만들기 - 2 (항목 지우기, 수정하기 기능 구현) (0) | 2022.11.20 |