표와 목록이 매우 크면 사이트 성능이 크게 저하될 수 있습니다. 가상화가 도움이 될 수 있습니다.
react-window
는 큰 목록을 효율적으로 렌더링할 수 있는 라이브러리입니다.
다음은 react-window
로 렌더링되는 행 1,000개가 포함된 목록의 예입니다. 최대한 빨리 스크롤해 보세요.
이것이 왜 유용할까요?
행이 여러 개 포함된 큰 테이블이나 목록을 표시해야 하는 경우가 있을 수 있습니다. 이러한 목록의 모든 항목을 로드하면 성능에 상당한 영향을 미칠 수 있습니다.
목록 가상화 또는 '윈도잉'은 사용자에게 표시되는 것만 렌더링하는 개념입니다. 처음에 렌더링되는 요소의 수는 전체 목록의 매우 적은 하위 집합이며 사용자가 계속 스크롤할 때 표시되는 콘텐츠의 '창'이 이동합니다. 이렇게 하면 목록의 렌더링과 스크롤 성능이 모두 향상됩니다.
![가상화된 목록의 콘텐츠 창](https://web.developers.google.cn/static/articles/virtualize-long-lists-react-window/image/window-content-a-virtua-9c3c7f0cbf9a5.jpg?authuser=19&hl=ko)
'창'을 나가는 DOM 노드는 재활용되거나 사용자가 목록을 아래로 스크롤할 때 새로운 요소로 즉시 대체됩니다. 이렇게 하면 창 크기에 따라 렌더링된 모든 요소의 수가 유지됩니다.
반응 창
react-window
는 애플리케이션에서 가상화된 목록을 쉽게 만들 수 있는 작은 서드 파티 라이브러리입니다. 이 라이브러리는 다양한 유형의 목록과 테이블에 사용할 수 있는 여러 기본 API를 제공합니다.
고정 크기 목록을 사용해야 하는 경우
크기가 동일한 항목으로 구성된 긴 1차원 목록이 있는 경우 FixedSizeList
구성요소를 사용합니다.
import React from 'react';
import { FixedSizeList } from 'react-window';
const items = [...] // some list of items
const Row = ({ index, style }) => (
<div style={style}>
{/* define the row component using items[index] */}
</div>
);
const ListComponent = () => (
<FixedSizeList
height={500}
width={500}
itemSize={120}
itemCount={items.length}
>
{Row}
</FixedSizeList>
);
export default ListComponent;
FixedSizeList
구성요소는height
,width
,itemSize
속성을 허용하여 목록 내 항목의 크기를 제어합니다.- 행을 렌더링하는 함수는
FixedSizeList
에 하위 요소로 전달됩니다. 특정 항목에 관한 세부정보는index
인수(items[index]
)를 사용하여 액세스할 수 있습니다. style
매개변수는 행 렌더링 메서드에 전달되며, 행 요소에 연결되어야 합니다. 목록 항목은 높이 및 너비 값이 인라인으로 할당되는 절대 위치로 배치되며style
매개변수가 이를 담당합니다.
이 문서의 앞부분에 나와 있는 Glitch 예는 FixedSizeList
구성요소의 예를 보여줍니다.
다양한 크기의 목록을 사용하는 경우
VariableSizeList
구성요소를 사용하여 크기가 다른 항목 목록을 렌더링합니다. 이 구성요소는 고정 크기 목록과 같은 방식으로 작동하지만, 대신 특정 값 대신 itemSize
속성을 위한 함수를 예상합니다.
import React from 'react';
import { VariableSizeList } from 'react-window';
const items = [...] // some list of items
const Row = ({ index, style }) => (
<div style={style}>
{/* define the row component using items[index] */}
</div>
);
const getItemSize = index => {
// return a size for items[index]
}
const ListComponent = () => (
<VariableSizeList
height={500}
width={500}
itemCount={items.length}
itemSize={getItemSize}
>
{Row}
</VariableSizeList>
);
export default ListComponent;
다음 삽입에서 이 구성요소의 예를 보여줍니다.
itemSize
속성에 전달된 항목 크기 함수는 이 예에서 행 높이를 무작위로 선택합니다. 하지만 실제 애플리케이션에는 각 항목의 크기를 정의하는 실제 로직이 있어야 합니다. 이러한 크기는 데이터를 기반으로 계산하거나 API에서 가져오는 것이 이상적입니다.
격자
react-window
는 다차원 목록 또는 그리드의 가상화도 지원합니다. 이 컨텍스트에서는 사용자가 가로 및 세로로 스크롤할 때 표시되는 콘텐츠의 '창'이 변경됩니다.
![가상화된 그리드에서 2차원으로 이동하는 콘텐츠 창](https://web.developers.google.cn/static/articles/virtualize-long-lists-react-window/image/moving-window-content-a-3393c2a96ff59.jpg?authuser=19&hl=ko)
마찬가지로 특정 목록 항목의 크기가 달라질 수 있는지에 따라 FixedSizeGrid
및 VariableSizeGrid
구성요소를 모두 사용할 수 있습니다.
FixedSizeGrid
의 경우 API는 거의 동일하지만 열과 행 모두의 높이, 너비, 항목 수를 나타내야 합니다.VariableSizeGrid
의 경우 값 대신 함수를 각 속성에 전달하여 열 너비와 행 높이를 모두 변경할 수 있습니다.
가상화된 그리드의 예는 문서를 참고하세요.
스크롤 시 지연 로드
대부분의 웹사이트는 사용자가 아래로 스크롤할 때까지 긴 목록의 항목을 로드하고 렌더링하기 위해 대기하여 성능을 개선합니다. 흔히 '무한 로드'라고 하는 이 기법은 사용자가 끝에 가까운 특정 임곗값을 스크롤할 때 목록에 새 DOM 노드를 추가합니다. 이 방법은 목록의 모든 항목을 한 번에 로드하는 것보다 낫지만 사용자가 그 이상으로 많은 항목을 스크롤하면 여전히 수천 개의 행 항목으로 DOM을 채웁니다. 이로 인해 DOM 크기가 과도하게 커져 스타일 계산 및 DOM 변형이 느려져 성능에 영향을 미치기 시작할 수 있습니다.
다음 다이어그램에 이 내용이 요약되어 있습니다.
![일반 목록과 가상화된 목록 간 스크롤의 차이점](https://web.developers.google.cn/static/articles/virtualize-long-lists-react-window/image/difference-scrolling-bet-517c003019905.jpg?authuser=19&hl=ko)
이 문제를 해결하는 가장 좋은 방법은 react-window
와 같은 라이브러리를 계속 사용하여 페이지 요소의 작은 '창'을 유지하는 동시에 사용자가 아래로 스크롤할 때 최신 항목을 지연 로드하는 것입니다. 별도의 패키지인 react-window-infinite-loader
를 사용하면 react-window
로 이를 실행할 수 있습니다.
상위 App
구성요소에서 관리되는 상태의 예를 보여주는 다음 코드를 살펴보세요.
import React, { Component } from 'react';
import ListComponent from './ListComponent';
class App extends Component {
constructor(props) {
super(props);
this.state = {
items: [], // instantiate initial list here
moreItemsLoading: false,
hasNextPage: true
};
this.loadMore = this.loadMore.bind(this);
}
loadMore() {
// method to fetch newer entries for the list
}
render() {
const { items, moreItemsLoading, hasNextPage } = this.state;
return (
<ListComponent
items={items}
moreItemsLoading={moreItemsLoading}
loadMore={this.loadMore}
hasNextPage={hasNextPage}
/>
);
}
}
export default App;
loadMore
메서드는 무한 로더 목록이 포함된 하위 ListComponent
에 전달됩니다. 이것이 중요한 이유는 무한 로더가 사용자가 특정 지점을 지나 스크롤하면 더 많은 항목을 로드하기 위해 콜백을 실행해야 하기 때문입니다.
목록을 렌더링하는 ListComponent
는 다음과 같습니다.
import React from 'react';
import { FixedSizeList } from 'react-window';
import InfiniteLoader from "react-window-infinite-loader";
const ListComponent = ({ items, moreItemsLoading, loadMore, hasNextPage }) => {
const Row = ({ index, style }) => (
{/* define the row component using items[index] */}
);
const itemCount = hasNextPage ? items.length + 1 : items.length;
return (
<InfiniteLoader
isItemLoaded={index => index < items.length}
itemCount={itemCount}
loadMoreItems={loadMore}
>
{({ onItemsRendered, ref }) => (
<FixedSizeList
height={500}
width={500}
itemCount={itemCount}
itemSize={120}
onItemsRendered={onItemsRendered}
ref={ref}
>
{Row}
</FixedSizeList>
)}
</InfiniteLoader>
)
};
export default ListComponent;
여기서 FixedSizeList
구성요소는 InfiniteLoader
내에 래핑됩니다.
로더에 할당된 props는 다음과 같습니다.
isItemLoaded
: 특정 항목이 로드되었는지 확인하는 메서드itemCount
: 목록의 항목 수 (또는 예상)loadMoreItems
: 목록의 추가 데이터로 확인되는 프로미스를 반환하는 콜백입니다.
렌더링 속성은 목록 구성요소가 렌더링하는 데 사용하는 함수를 반환하는 데 사용됩니다.
onItemsRendered
및 ref
속성은 모두 전달해야 하는 속성입니다.
다음은 가상화된 목록에서 무한 로드가 작동하는 방식을 보여주는 예입니다.
목록을 아래로 스크롤하는 것과 같은 느낌이 들 수 있지만, 이제 목록의 끝까지 스크롤할 때마다 임의의 사용자 API에서 10명의 사용자를 검색하라는 요청이 전송됩니다. 이 작업은 한 번에 하나의 결과 '기간'만 렌더링하는 동안 수행됩니다.
특정 항목의 index
를 확인하면 최신 항목에 관한 요청이 이루어졌고 항목이 아직 로드 중인지에 따라 항목의 다른 로드 상태가 표시될 수 있습니다.
예를 들면 다음과 같습니다.
const Row = ({ index, style }) => {
const itemLoading = index === items.length;
if (itemLoading) {
// return loading state
} else {
// return item
}
};
오버스캔
가상화된 목록의 항목은 사용자가 스크롤할 때만 변경되므로 최신 항목이 표시되려고 할 때 빈 공간이 잠시 깜박일 수 있습니다. 이 가이드의 이전 예를 빠르게 스크롤하여 이를 확인할 수 있습니다.
가상화된 목록의 사용자 환경을 개선하기 위해 react-window
에서는 overscanCount
속성으로 항목을 오버스캔할 수 있습니다. 이를 통해 표시되는 '창' 외부의 항목 수를 항상 정의할 수 있습니다.
<FixedSizeList
//...
overscanCount={4}
>
{...}
</FixedSizeList>
overscanCount
는 FixedSizeList
및 VariableSizeList
구성요소에서 모두 작동하며 기본값은 1입니다. 목록의 크기와 각 항목의 크기에 따라 두 개 이상의 항목을 오버스캔하면 사용자가 스크롤할 때 눈에 띄는 빈 공간이 눈에 띄지 않도록 방지할 수 있습니다. 하지만 너무 많은 항목을 오버스캔하면 성능에 부정적인 영향을 줄 수 있습니다. 가상화된 목록을 사용하는 요점은 사용자가 특정 순간에 볼 수 있는 항목으로 항목 수를 최소화하는 것이므로 오버스캔된 항목 수를 가능한 한 적게 유지하는 것이 좋습니다.
FixedSizeGrid
및 VariableSizeGrid
의 경우 overscanColumnsCount
및 overscanRowsCount
속성을 사용하여 각각 오버스캔할 열과 행 수를 제어합니다.
결론
애플리케이션에서 목록 및 테이블 가상화를 시작할 위치를 잘 모르는 경우 다음 단계를 따르세요.
- 렌더링 및 스크롤 성능 측정 이 도움말은 Chrome DevTools의 FPS 측정기를 사용하여 목록에서 항목이 얼마나 효율적으로 렌더링되는지 살펴보는 방법을 보여줍니다.
- 성능에 영향을 미치는 긴 목록이나 그리드의 경우
react-window
를 포함합니다. react-window
에서 지원되지 않는 특정 기능이 있다면 이 기능을 직접 추가할 수 없다면react-virtualized
를 사용해 보세요.- 사용자가 스크롤할 때 항목을 지연 로드해야 하는 경우 가상화된 목록을
react-window-infinite-loader
로 래핑합니다. - 빈 콘텐츠가 플래시되지 않도록 목록에는
overscanCount
속성을 사용하고 그리드에는overscanColumnsCount
및overscanRowsCount
속성을 사용합니다. 너무 많은 항목을 오버스캔하지 마세요. 성능에 부정적인 영향을 미칩니다.