[react-window] 대규모 데이터를 virtualize table 로 랜더링하기
최근 테이블을 구현하는데 있어서, 10만개가 넘는 대규모 데이터로 구현할 일이 생겼다.
기존 코드에선 <table> 엘리먼트를 사용해서 내가 알던 방식으로 table을 구현했는데,
브라우저가 렉 걸리고... 멈춰버리는 현상이 발생했다.
아마도 데이터의 양이 많다보니 모든 행과 열을 한 번에 랜더링 하는과정에서 발생하는 문제인 것 같은데,
이를 해결하기 위해서 가상화 테이블 ( virtualized table ) 을 구현하는 방법에 대해서 알아보았다.
먼저 table 엘리먼트와 react-virtualized를 이용한 table의 차이점에 대해서 설명하겠다.
1. 성능 및 가상화
위에서 언급했듯이 table 엘리먼트는 대규모 데이터를 랜더링하는데 적합하지 않다.
react-virtualized의 경우 화면에 보이는 아이템만 랜더링하기때문에
가상화 ( 테이블 아이템이 있다고 테이블의 가상공간을 미리 만들어 둔다고 생각하면 편하다. but 실제로는 data를 랜더링 한 것이 아니기 때문에 성능은 기존 table 엘리먼트보다 훨씬 좋을 수 밖에 없다. ) 를 통해 성능을 크게 향상시켜준다.
2. DOM 요소 개수
- table 엘리먼트를 사용한 테이블은 행 수에 비례해서 DOM 요소의 수가 증가한다. 따라서 메모리 사용 및 브라우저 성능에 부정적인 영향을 미친다.
- react-virtualized를 사용하게 되면 화면에 표시되는 행 수와 관계없이 제한된 개수의 DOM 요소만 생성되기 때문에 메모리 사용이 효율적이다.
3. 코드 복잡성 및 유연성
- table 엘리먼트의 경우 표준 HTML 요소를 사용하기 때문에 구현하기 쉽고 이해하기가 쉽다. 반면 react-virtualized의 경우 추가 구성 및 커스터마이징이 필요하기 때문에 초기 설정이 더 복잡해질 순 있다.
- table 엘리먼트의 경우 기본적으로 표준 테이블 마크업을 생성하므로 브라우저에서 기본 스타일 및 동작을 제공해준다. 반면 react-virtualized의 경우 스타일 및 동작을 완전히 사용자 정의할 순 있지만 이는 추가적인 작업이 필요하다.
이러한 이유들을 보았을 때, 대규모 데이터를 랜더링 하는 환경에서는 react-virtualized를 적절히 사용한다면 훨씬 효율적이란 걸 알 수 있다.
이어서 실제로 react-virtualized를 사용해서 코드를 구현해보았다.
1. react-virtualized
첫 번째로 적용해 본 라이브러리는 react-virtualized이다.
( 왜 첫 번째가 존재하냐면... 결론부터 말하자면 해당 라이브러리말고 이어서 서술 할 후자를 선택했다. )
https://github.com/bvaughn/react-virtualized
GitHub - bvaughn/react-virtualized: React components for efficiently rendering large lists and tabular data
React components for efficiently rendering large lists and tabular data - GitHub - bvaughn/react-virtualized: React components for efficiently rendering large lists and tabular data
github.com
<Table
width={300}
height={300}
headerHeight={20}
rowHeight={30}
rowCount={list.length}
rowGetter={({ index }) => list[index]}
>
<Column
label='Name'
dataKey='name'
width={100}
/>
<Column
width={200}
label='Description'
dataKey='description'
/>
</Table>
이러한 형태로 코드를 작성하게 되고, 방식은 Table 태그안에 각 Column들을 랜더링 시켜줘서 테이블을 만들게 된다.
코드 구현도 어렵지 않고 생각보다 커스터마이징도 쉬웠다.
근데 아쉬운 점들이 몇 가지 발생했다. 개발도중에 table 관련 에러가 발생했을 때 구글링을 하면 생각보다 관련 정보가 없었다. 그리고 스타일 커스터마이징을 하는데 grid나 padding 조작을 할 때 커스터마이징의 한계점들이 존재했다...
그러는 와중에 알고보니 이게 웬걸..
react-window 라고 기존 react-virtualized의 업그레이드 느낌의 라이브러리가 존재했다.
react-virtualized를 만든 개발자들이 그 당시 아쉬웠던 내용들을 보완해서 만들어 낸 라이브러리라고 한다.
아래 링크를 통해 들어가면 react-virtualized 와 react-window를 개발자들이 직접 비교한 내용이 있다.
https://github.com/bvaughn/react-window#how-is-react-window-different-from-react-virtualized
GitHub - bvaughn/react-window: React components for efficiently rendering large lists and tabular data
React components for efficiently rendering large lists and tabular data - GitHub - bvaughn/react-window: React components for efficiently rendering large lists and tabular data
github.com
요약하자면, 유지 및 확장성이 좋아졌고 기존 라이브러리에 비해서 필요한 기능만 넣어둬서 훨씬 가벼워졌다는 것이다.
더불어 관련 업데이트도 꾸준하고 커뮤니티도 활성화 되어있다..! ( 제일 중요함 )
어쨌든 결론은 react-window를 사용하게 되었다는 것이다.
2. react-window
코드 형태는 위의 react-virtualized 라이브러리를 사용했을 때와 비슷하지만,
훨씬 커스터마이징도 쉽고 기능 작동도 잘 되었다.
<FixedSizeList
className={styles.table}
height={140}
itemCount={totalRows}
itemSize={30}
width={(modelList.length - 1) * 40 + columnWidth}
ref={tableRef}
>
{renderRow}
</FixedSizeList>
난 FixedSizeList 라고, 고정된 크기를 미리 지정해주는 컴포넌트를 사용하였다.
해당 코드에서 modelList 가 열의 값, renderRow에 각 행 컴포넌트가 들어가게 된다.
( row 관련 코드가 길어질 것 같아 따로 분리하였다. )
const renderRow = ({
index,
style,
}: {
index: number;
style: React.CSSProperties;
}) => {
const row = transformedData[index];
return (
<div
className="data-row"
style={{ ...style, display: "flex" }}
>
<div
className="data-cell"
style={{
width: `${columnWidth}px`,
border: "1px solid #C4CBDA",
fontWeight: "bold",
}}
>
// header cell 이 될 내용
각 row의 header 값
</div>
{modelList.map((model, columnIndex) => (
<div
key={model}
className="data-cell"
style={{ width: `40px`, border: "1px solid #C4CBDA" }}
>
{row[model] !== null ? row[model].toLocaleString() : 0}
</div>
))}
</div>
);
};
위와 같이 사용하게되면 사용방식도 어렵지 않다.
처음에 각 row의 맨 앞에 들어 갈 header cell의 내용을 만들어주고, 이어지는 cell들은 map을 통해서 구현해주면 된다.
코드를 보면 알겠지만, 스타일을 inline태그로 저리 작성해도 되고 그냥 className을 추가해서 따로 css를 작성해주어도 된다.
이렇게 쉽게 라이브러리를 사용하면,
놀랍게도 대규모 데이터도 랜더링이 바로바로 되고 테이블은 virtualize 가 되어서, 스크롤을 한 뒤에 데이터를 랜더링 해온다. ( 테이블 영역은 빈 공간으로 보이다가, 스크롤이 멈추면 데이터를 랜더링 해서 불러와서 테이블을 채워넣음 )
데이터의 양이 적다면 table 엘리먼트를 사용해서 테이블을 구성하는 것도 좋지만,
프로젝트의 데이터 양은 시간이 지나고 바뀔수도 있다고 생각한다.
그런 일을 대비한다는 생각으로 react-window를 사용해서 테이블을 가상화 시켜두게 된다면,
기능상으로 문제없고 성능도 좋아질 것이다. 그러므로 앞으로는 테이블은 해당 라이브러리를 사용해서 개발 할 생각이다.