가상 리스트, 가상 스크롤, 목록 가상화 등 다양한 이름으로 불리는 최적화 방식에 대해서 알아보자.
이 방식은 동적인 목록을 렌더링할때, 특히 대량의 리스트를 랜더링할 때, 전체 목록을 렌더링하지 않고 화면에 “보이는” 컨텐츠 들만 랜더링하는 방식이다.
목록의 요소들을 가상화하기 위해서는 윈도우를 고정시키고 목록 주변에서 윈도우를 움직여야 한다.
// 리스트 아이템 하나의 높이
const ItemHeightSize = 50;
//
function RecylerView({items}: Props) {
// 목록의 시작인덱스, 끝 인덱스를 상태로 관리하며
// 사용자가 스크롤할때마다 보여줄 목록의 인덱스를 업데이트 한다.
// 여기서 보여줄 목록의 갯수는 20개
const [visiableStartIndex, setVisiableStartIndex] = useState(0);
const [visiableEndIndex, setVisiableEndIndex] = useState(20);
const containerRef = useRef<HTMLDivElement>(null);
// 스크롤 할때마다 start, end index update
const handleScroll = () => {
if(containerRef.current) {
const scrollTop:number = containerRef.current.scrollTop;
const newStartIndex = Math.floor(scrollTop/ ItemHeightSize);
const visibleItemCount = Math.ceil(window.innerHeight/ ItemHeightSize);
const newEndIndex = newStartIndex + visibleItemCount;
setVisiableStartIndex(newStartIndex);
setVisiableEndIndex(newEndIndex);
}
}
// 이벤트 핸들러, 최초 1회 등록
useEffect(() => {
if(containerRef.current){
containerRef.current.addEventListener("scroll", handleScroll)
return () => {
if(containerRef.current){
containerRef.current.removeEventListener("scroll", () => {})
}
}
}
}, [])
return(
// container : 스크롤을 위한 큰 돔 엘리먼트
<div style={{ overflowY:"scroll", height:"100vh"}} ref={containerRef}>
// 이녀석이 Window : relative 속성을 가지는 작은 컨테이너 돔 엘리먼트
<ul style={{ height: ItemHeightSize * items.length, position:"relative" , width:500}}>
{items.slice(visiableStartIndex, visiableEndIndex).map((item: number, index: number) => (
// 컨테이너 내부에 위치하고
// absolute 포지션 속성을 가지고
// top | left | width |. height 속성을 가지는 자식 요소들
<li key={index} style={{height: `${ItemHeightSize}px`, position: "absolute", top: (visiableStartIndex+index)*ItemHeightSize}}>{item}</li>
))}
</ul>
</div>
)
}
https://patterns-dev-kr.github.io/performance-patterns/list-virtualization/