React + React-virtualized 를 조합한 무한 스크롤링 구현에 대해 다루고 있습니다.
목차- 무한스크롤링이란 무엇인가?
- 무한스크롤링을 개선하는 두가지 방법
- 리액트에서 무한스크롤링 최적화를 도와주는 React-virtualized 사용해보기
무한스크롤링이란 무엇인가 ?
여러분들도 한번쯤은 무한 스크롤링 처리가 되어있는 웹사이트를 이용해보신 경험이 있을 것 입니다.
보통 컨텐츠를 로드할때는 수천 수만개의 컨텐츠를 한번에 로드 하는 것이 아닌,
사용자가 스크롤을 내릴때마다 10개 씩 20개씩 불러옵니다.
무한 스크롤 자체의 구현은 그리 어렵지 않았습니다.
내 스크롤이 창 아래에 닿으면 새로운 게시물을 불러오면 됩니다.
하지만, 로드한 컨텐츠가 많아지면 많아질 수록 리플로우의 계산횟수가 많아지기에 렌더링 속도는 더욱 느려질 겁니다.
무한스크롤링 개선기
네이버 D2에서 진행한 세션을 참고해 알아보았습니다.
무한 스크롤링 시 일어나는 렌더링 속도 저하를 위해 생각해낸 방법은 보이는 부분만 렌더링 하자! 입니다.
이를 위해 우리는 두가지 방법을 이용할 수 있습니다.
display: none
dom이 그려지는 과정을 이해하고 있다면,
display: none은 렌더링 비용을 낮출 수 있다는 것을 알고 있을 것입니다.
하지만 퍼포먼스 성능의 향상은 크게 이루지 못했습니다.
아래는 Flicking 라이브러리의 각 렌더링 overview 입니다.
기존의 렌더링 비용 overview
display: none overview
기존 보다는 개선이 되었지만 여전히 Item 수가 많아질 수록 렌더링 비용도 동시에 증가합니다.
DOM 개수 줄이기
"DOM 개수 줄이기" 를 생각해 보았습니다. 보이지 않는 DOM은 삭제를 시켜 버리는 방법입니다.
렌더링 해야할 DOM의 개수가 많아질 수록 렌더링 비용이 증가하는 display: none과 달리 DOM을 삭제하는 경우 그러한 현상이 보이지 않는걸 확인할 수 있습니다.
리액트에서 무한스크롤링 최적화를 도와주는 React-virtualized 사용해보기
위의 DOM삭제를 직접 구현할 수는 있지만, 이를 도와주는 라이브러리가 있습니다.
바로 React-virtualized 입니다.
우선 여러분이 사용하는 프로젝트에 React-virtualized를 install 합니다.
npm install react-virtualized --save
근데 간혹 의존성 에러가 난다는 사람들이 많더군요.
그럴땐 아래 명령어로 설치를 진행하시면 됩니다.
npm install react-virtualized --legacy-peer-deps
"the workaround is using --legacy-peer-deps, but it would be great if react-virtualized would officially support React 17." - 리액트 17을 지원하지 않아 발생하는 문제 라고 하네요.
그리고 샘플 데이터를 만들어 줄건데 각 데이터 별로 랜덤한 아이디를 주기 위해
uuid를 설치해보도록 하겠습니다.
npm i uuid --save
우선 무한스크롤을 구현하기 앞서 임시의 가짜데이터를 만들어주겠습니다.
import React, { useState } from 'react'; import { v4 as uuidv4 } from 'uuid'; const Sample1 = () => { const mockData = [ { id: uuidv4(), content: "content입니다." }, { id: uuidv4(), content: "content입니다." }, { id: uuidv4(), content: "content입니다." }, { id: uuidv4(), content: "content입니다." }, ]; const [data, setData] = useState(mockData); return ( <div></div> ) } export default Sample1;
이번 react-virturelized예제 에서 사용할 컴포넌트들은 다음과 같습니다.
AutoSizer
부모 element의 너비와 높이를 자식 컴포넌트에 전달해주는 HOC입니다.
이걸 활용하면 부모의 너비와 높이만큼 자식을 꽉 채울 수 있습니다.
List
이 구성 요소는 요소의 창 목록 (행)을 렌더링합니다.
react-virturelized에서 무한 스크롤을 구현할땐 InfiniteLoader를 사용하시는게 좋긴 합니다만,
간단하게 이 두가지 요소를 활용해 무한스크롤링을 구현해보도록 하겠습니다.
실제로 이렇게만 써도 큰문제는 없습니다.
기본 구조는 다음과 같습니다.
<AutoSizer AutoSizer> {({width}) => ( <List rowCount={data.length} // 항목의 개수 height={500} // 실제 렌더링 되는 높이범위 rowHeight={50} // 항목의높이 width={width} // 항목의 너비 rowRenderer={rowRanderer} // 항목렌더링할때쓰는 함수 onScroll={scrollListener} // scroll 함수 overscanRowCount={5} // 다음에 로드해올 항목 미리 컨텐츠 높이 잡기 /> )} </AutoSizer>
이제 rowRenderer, onScroll를 만들어보겠습니다.
const rowRanderer = ({ index, style }) => { const post = data[index]; return ( <div style={style}> {post.content} </div> ); };
const scrollListener = (params) => { if (params.scrollTop + params.clientHeight >= params.scrollHeight - 300) { if (data.length <= 100) { setData([ ...data, { id: uuidv4(), content: "추가된 content입니다." }, { id: uuidv4(), content: "추가된 content입니다." }, { id: uuidv4(), content: "추가된 content입니다." }, { id: uuidv4(), content: "추가된 content입니다." }]) } } };
onScroll에 들어가는 함수는 인자값으로 scrollTop, clientHeight, scrollHeight 이 들어옵니다.
params.scrollTop + params.clientHeight >= params.scrollHeight - 300
params.scrollTop + params.clientHeight >= params.scrollHeight - 300
스크롤의 위치가 바닥에 닿기 전 살짝 위에서부터 데이터를 추가하도록 만들었습니다.
rowRenderer 은 실제로 컨텐츠를 그릴 렌더링 함수 입니다.
여기서 많이들 실수 하시는데
로드할 컨텐츠의 인덱스와 스타일을 꼭 부여해주어야합니다.
const post = data[index]; // index는 react-virturized에서 새로 그릴 아이템의 index를 구해 인자로 넘겨준다.
<div style={style}> {post.content} </div>
이제 확인해보시면 스크롤이 바닥에 닿을때마다 컨텐츠가 추가되지만, 실제로 화면에 보이는 돔만 그려지는 걸 알 수 있습니다.
전체코드
import React, { useState } from 'react'; import { AutoSizer, List } from 'react-virtualized'; import { v4 as uuidv4 } from 'uuid'; const Sample1 = () => { const mockData = [ { id: uuidv4(), content: "content입니다." }, { id: uuidv4(), content: "content입니다." }, { id: uuidv4(), content: "content입니다." }, { id: uuidv4(), content: "content입니다." }, ]; const [data, setData] = useState(mockData); const scrollListener = (params) => { if (params.scrollTop + params.clientHeight >= params.scrollHeight - 300) { if (data.length <= 100) { setData([ ...data, { id: uuidv4(), content: "추가된 content입니다." }, { id: uuidv4(), content: "추가된 content입니다." }, { id: uuidv4(), content: "추가된 content입니다." }, { id: uuidv4(), content: "추가된 content입니다." }]) } } }; const rowRanderer = ({ index, style }) => { const post = data[index]; return ( <div style={style}> {post.content} </div> ); }; return ( <AutoSizer AutoSizer> {({width}) => ( <List rowCount={data.length} // 항목의 개수 height={400} // 실제 렌더링 되는 높이범위 rowHeight={200} // 항목의높이 width={width} // 항목의 너비 rowRenderer={rowRanderer} // 항목렌더링할때쓰는 함수 onScroll={scrollListener} // scroll 함수 overscanRowCount={2} // 다음에 로드해올 항목 미리 컨텐츠 높이 잡기 /> )} </AutoSizer> ) } export default Sample1;
참고자료
https://github.com/bvaughn/react-virtualized
https://stackoverflow.com/questions/38762914/infiniteloader-and-react-redux
저는 벨로그와 티스토리 모두 운영하고 있어 벨로그에서 저와 같은 글을 발견한다면 아마도 저입니다.
'React' 카테고리의 다른 글
🤩 rollup + typescript + react 세팅하기 (0) | 2021.04.26 |
---|---|
Error 정리: React-Uncaught SyntaxError: Unexpected token < (3) | 2021.02.24 |
WebSoket (stompJS+React) 채팅 (6) | 2021.02.14 |
useEffect 에 대하여 (0) | 2020.11.27 |
React에의 서버사이드렌더링(SSR) vs 클라이언트사이드렌더링(CSR) (0) | 2020.10.23 |