[React] Infinite Scroll 구현
React의 렌더링 퍼포먼스 개선기 (부제: 1000개 이상의 아이템을 가진 리스트를 개선하기)
안녕하세요, 미누캉입니다. 근 2년 오랜만에 글을 쓰게되었는데요, 그 사이에 위버스 컴퍼니 웹개발팀으로 이직을 하게 되었습니다 😇
medium.com
https://markoskon.com/displaying-hundreds-of-images-with-react-window-and-gatsby-image/
Displaying hundreds of images with react-window and gatsby-image | Dev Diary
See how to display hundreds of images with react-window and gatsby-image by building an app that displays all the Hearthstone cards in a grid. You can also see the compromises you have to make.
markoskon.com
스크롤형태의 많은 데이터를 보여주기 위해 사용
위 라이브러리 사용하지않고 만들때는 데이터가 많아 질수록 굉장히 느려지는 현상이 발생하여
현재 보여지는 부분만 DOM에 그려지는 위 라이브러리 사용
<EasyVirtualizedScroller
hasMore={true}
onLoadMore={loadMore}
useParentScrollElement={true}
overscanRowCount={5}
loader={<SkeletonGallery/>}
>
{
galleryList.map( (element, index) => {
if(element.title !== ''){
return (
<ButtonBase
sx={
{ width: 500, height: 190, textAlign: 'left', flexWrap: 'nowrap'
}}
key={element.id}
onClick={() => handleClickGallery(element.url, element.galleryId)}
>
<Gallery
{...element}
/>
</ButtonBase>
)
}else{
return (
<SkeletonGallery key={index}/>
)
}
})
}
</EasyVirtualizedScroller>
react-virtualized 라이브러리 사용 시 스크롤 마다 전체 데이터 리스트가 다 render 되는게 발생해서
react-easy-virtualized 라이브러리를 사용하였지만
알고보니 memoize-one 라이브러리를 사용하여 추가 최적화를 진행해주면 스크롤 마다 render 되는일은 발생하지않음
리스트는 react-easy-virtualized를 사용하여 구현
그리드는 reac-window를 사용하여 구현
<Paper
sx={{
p: 2,
maxWidth: 1120,
height: 690,
flexGrow: 1,
backgroundColor: (theme) =>
theme.palette.mode === 'dark' ? '#1A2027' : '#fff',
}}
>
<AutoSizer>
{({ width, height }) => {
const columnCount = Math.floor(width / gridOption.imgWidth);
const itemData = createItemData(galleryInfo.data.files, columnCount);
const rowCount = Math.ceil(galleryInfo.data.files.length / columnCount);
return (
<FixedSizeGrid
className="grid"
width={width}
height={height}
columnCount={columnCount}
columnWidth={gridOption.imgWidth}
rowCount={rowCount}
rowHeight={gridOption.imgHeight}
itemData={itemData}
style={{marginLeft: '33px'}}
>
{Cell}
</FixedSizeGrid>
)
}}
</AutoSizer>
</Paper>
동적배열 형태가 아니기때문에 스크롤 시 모든 데이터가 render 되게 하지않게 하기위해서는
memoize-one 라이브러리로 최적화가 필요하다
const createItemData = memoizeOne((imageData, columnCount) => ({
imageData,
columnCount
}));
const Cell = memo(({ columnIndex, rowIndex, style, data }: any) => {
const { imageData, columnCount } = data
const singleColumnIndex = columnIndex + rowIndex * columnCount
const image = imageData[singleColumnIndex]
if(singleColumnIndex >= imageData.length) return ( <></>);
return (
<div style={{ ...style, }}>
{
image && image.img !== ''
?
<GalleryImg
id={image.id}
img={image.img}
/>
:
<Skeleton/>
}
</div>
)
}, areEqual)
각 행의 그리는 Cell 함수에 넘겨주는 데이터를 생성하는 createItemData 함수를
최초 1번만 호출하고 그 후에는 memoize 한 결과값을 리턴하기 때문에 스크롤 마다 모든 데이터가 render 되지않음
내 스크롤에는 각 행마다 이미지가 들어있기 때문에
스크롤 시 마다 새로 그려지는 행의 이미지를 매번 가져오는 문제가 있어
useSWR을 이용하여 이미지들을 전역 상태로 저장하여 관리한다