Skip to content

InfiniteList

InfiniteList는 데이터 페칭 로직과 가상화 기술을 결합하여, data를 fetching하며 무한히 스크롤되는 대용량 리스트를 효율적으로 구현할 수 있게 도와줍니다.

사용 예시

tsx
import { InfiniteList } from "@scrolloop/react";

function App() {
  const fetchPage = async (page: number, size: number) => {
    const response = await fetch(
      `https://api.example.com/items?page=${page}&size=${size}`
    );
    return await response.json(); // { items: T[], total: number, hasMore: boolean } 반환
  };

  return (
    <InfiniteList
      fetchPage={fetchPage}
      itemSize={50}
      pageSize={20}
      renderItem={(item, index, style) => (
        <div key={index} style={style}>
          {item ? item.text : "Loading..."}
        </div>
      )}
    />
  );
}
tsx
import { InfiniteList } from "@scrolloop/preact";

export function App() {
  const fetchPage = async (page: number, size: number) => {
    const response = await fetch(`/api/items?page=${page}&size=${size}`);
    return response.json(); // { items, total, hasMore }
  };

  return (
    <InfiniteList
      fetchPage={fetchPage}
      itemSize={50}
      renderItem={(item, index, style) => (
        <div style={style}>{item ? item.title : "Loading..."}</div>
      )}
    />
  );
}
vue
<script setup lang="ts">
import { InfiniteList } from "@scrolloop/vue";

const fetchPage = async (page: number, size: number) => {
  const response = await fetch(`/api/items?page=${page}&size=${size}`);
  return response.json(); // { items, total, hasMore }
};
</script>

<template>
  <InfiniteList :fetch-page="fetchPage" :item-size="50" :height="400">
    <template #default="{ item, style }">
      <div :style="style">{{ item?.title ?? "Loading..." }}</div>
    </template>
  </InfiniteList>
</template>
svelte
<script lang="ts">
  import { InfiniteList } from "@scrolloop/svelte";

  const fetchPage = async (page: number, size: number) => {
    const response = await fetch(`/api/items?page=${page}&size=${size}`);
    return response.json(); // { items, total, hasMore }
  };
</script>

<InfiniteList {fetchPage} itemSize={50} height={400}>
  {#snippet children(index, item, style)}
    <div
      style={`position: ${style.position}; top: ${style.top}; left: ${style.left}; right: ${style.right}; height: ${style.height};`}
    >
      {item?.title ?? "Loading..."}
    </div>
  {/snippet}
</InfiniteList>
tsx
import { View, Text } from "react-native";
import { InfiniteList } from "@scrolloop/react-native";

function App() {
  const fetchPage = async (page: number, size: number) => {
    return { items: data, total: 1000, hasMore: page < 49 };
  };

  return (
    <View style={{ flex: 1 }}>
      <InfiniteList
        fetchPage={fetchPage}
        itemSize={60}
        height={800}
        renderItem={(item, index, style) => (
          <View style={style}>
            <Text>{item ? item.title : "로딩 중..."}</Text>
          </View>
        )}
      />
    </View>
  );
}

Props

InfiniteList는 가상화 설정 외에도 데이터 페칭 및 상태 관리를 위한 다양한 Props를 제공합니다. 모든 패키지(react, react-native)에서 공통으로 지원하는 Props는 다음과 같습니다.

PropTypeRequiredDescription
fetchPageFunctionYes페이지별 데이터를 가져오는 비동기 함수입니다. (page, size) => Promise<PageResponse<T>> 형태여야 합니다.
renderItemFunctionYes각 아이템을 렌더링하는 함수입니다. (item, index, style) => ReactNode 형태입니다.
itemSizenumberYes각 아이템의 고정된 높이(또는 너비)입니다.
pageSizenumberNo한 페이지당 아이템의 개수입니다. (기본값: 20)
initialPagenumberNo처음 로드할 페이지 번호입니다. (기본값: 0)
prefetchThresholdnumberNo현재 범위 뒤로 추가로 미리 불러올 페이지 수입니다. (기본값: 1, React/React Native/Vue/Svelte)
heightnumberNo리스트 컨테이너의 높이입니다. (기본값: 400)
overscannumberNo뷰포트 외부에서 미리 렌더링할 아이템 수입니다. (기본값: Math.max(20, pageSize * 2))
renderLoadingFunctionNo최초 로딩 중에 표시할 UI를 렌더링하는 함수입니다.
renderErrorFunctionNo에러 발생 시 표시할 UI를 렌더링하는 함수입니다. (error, retry) => ReactNode 형태입니다.
renderEmptyFunctionNo데이터가 없을 때 표시할 UI를 렌더링하는 함수입니다.
onPageLoadFunctionNo페이지 로드가 성공했을 때 실행되는 콜백입니다.
onErrorFunctionNo에러가 발생했을 때 실행되는 콜백입니다.

PageResponse<T>는 다음 형태입니다.

ts
interface PageResponse<T> {
  items: T[];
  total: number;
  hasMore: boolean;
}

React 전용 (@scrolloop/react)

React 환경에서는 SSR 및 성능 최적화를 위한 추가 옵션을 제공합니다.

  • isServerSide (boolean): 서버 사이드 렌더링(SSR) 모드를 활성화합니다. 활성화 시 하이드레이션 전까지 전체 리스트를 렌더링합니다.
  • initialData (T[]): SSR 환경에서 서버로부터 미리 받아온 초기 데이터입니다.
  • initialTotal (number): 전체 아이템의 총 개수를 서버에서 미리 알고 있는 경우 전달합니다.
  • transitionStrategy (object): SSR에서 가상화 리스트로 전환될 때의 상세 전략을 설정합니다.

Preact 전용 (@scrolloop/preact)

  • class: 컨테이너 요소에 적용할 CSS 클래스입니다.
  • style: 컨테이너 요소에 적용할 인라인 스타일입니다.
  • 현재 Preact adapter는 prefetchThreshold prop을 노출하지 않습니다. 필요한 페이지 범위와 다음 페이지를 자동으로 로드합니다.

Vue 전용 (@scrolloop/vue)

  • 기본 slot은 { item, index, style }을 전달합니다.
  • loading, error, empty named slot을 사용할 수 있습니다.
  • pageLoad, error 이벤트로 로딩 결과를 받을 수 있습니다.

Svelte 전용 (@scrolloop/svelte)

  • children snippet은 (index, item, style)을 전달받습니다.
  • loading, error, empty snippet을 사용할 수 있습니다.

React Native 전용 (@scrolloop/react-native)

  • React Native adapter는 onScroll을 제외한 ScrollViewProps를 전달할 수 있습니다.
  • SSR 관련 props는 React DOM adapter에서만 지원합니다.

작동 방식

  1. Lazy Loading: InfiniteList는 사용자의 스크롤 위치를 감시하며, 화면에 노출될 것으로 예상되는 페이지가 아직 로드되지 않은 경우에만 fetchPage를 호출합니다.
  2. Skeleton 지원: data가 로딩 중일 때 renderItemundefined를 넘겨주어, skeleton UI를 쉽게 구현할 수 있도록 합니다.
  3. 자동 재시도: 네트워크 오류 등으로 페칭에 실패한 경우, renderError 또는 error slot/snippet에서 제공하는 retry 함수를 통해 initialPage부터 다시 불러올 수 있습니다.