React + GraphQL + Apollo로 무한 스크롤 (Offset-based)
무한 스크롤 그 자체보다, GraphQL의 페이지 네이션의 구현으로 생각하는 것이 많은 생각이 든다.
↓실장 이미지
1. 전제 지식·조건
1-1. 구현 환경 및 라이브러리
클라이언트는 React(Javascript), 서버측은 Hasura에서 postgreSQL과 연결하도록 하고 클라이언트측 GraphQL의 처리는 Apollo를 사용."@apollo/client": "^3.3.19
"react": "^17.0.2
Hasura v1.3.3
무한 스크롤 구성 요소는 react-infinite-scroll-component를 사용합니다."react-infinite-scroll-component": "^6.1.0"
1-2.무한 스크롤이란?
무한 스크롤은 Google 검색 결과와 같은 페이지 전환이 아니라 스크롤로 차례로 콘텐츠를 표시시키는 UI입니다. Twitter라든지 인스타 같은 느낌.
모든 콘텐츠를 표시하는 대신 일정량의 콘텐츠를 표시하여 페이지 하단에 도달했을 때 (전에) 다음 콘텐츠를로드해야합니다.
1-3.2 종류의 페이지 네이션에 대해서
일정량의 콘텐츠의 표시(페이지 네이션) 구현은 offset-based와 cursor-based의 2종류로 크게 나눌 수 있다.
offset-based
Limit과 Offset을 지정하여 데이터 취득할 때마다 Offset의 수를 늘려 데이터 취득 범위를 늘려가는 이미지. 구현은 쉽지만 데이터량이 늘어나면 데이터를 보는 양이 늘어나 성능이 나빠지는 것 같다. SQL의 LIMIT와 OFFSET 사용하는 느낌.
cursor-based
데이터 취득 범위의 최초와 마지막을 지정해, 데이터 취득하는 이미지. SQL로 말한다면 Where 절로 데이터를 좁히는 느낌. GraphQL로 하는 경우, 취득하는 데이터에 일의이고 순서가 있는 컬럼(unique, sequential인 컬럼)을 갖게 할 필요가 있는 것 같다.
이 근처의 자세한 해설은 ↓의 기사가 굉장히 참고가 된다.
cursor-based 안에 한층 더 relay-style-pagination이라고 하는 수법이 있어, Apollo라고 InMemoryCache로 relayStylePagination()를 지정하면 좋은 느낌으로 할 수 있는 것 같지만, ID의 흔들림등으로 막혔으므로 여기에서는 스루.
1-4. Apollo의 fetchMore 소개
fetchMore는 useQuery 훅에 포함되는 함수로, useQuery의 쿼리를 인수만큼 바꾸어 실행하고 싶을 때에 사용한다.
스크롤로 다음 콘텐츠를 로드할 때마다 fetchMore를 실행하는 느낌.
1-5.react-infinite-scroll-component 정보
스크롤하려는 요소를 태그 바로 아래의 자식 요소로 배치합니다.
↓사용할 때의 이미지
infiniteScroll.js<InfiniteScroll
//表示する子要素の数(スクロールする度に増やす必要がある)
dataLength={items.length}
//ページ下部に到達した時に実行する関数を指定
next={fetchData}
//データ取得がを続けるかどうかを指定。
//trueだとページ下部に到達した時にnextで指定した関数が実行さる。
//falseだと実行されず、endMessageが表示される。
hasMore={true}
//nextの関数結果が返ってくるまで表示するう要素を指定
loader={<h4>Loading...</h4>}
//hasMoreがfalseの時に表示する要素を指定
endMessage={<p>You have seen it all</p>}
>
//スクロールさせたい要素を列挙
{sampleData.map((v) =>
<sampleDataChild key={v.key}/>)}
</InfiniteScroll>
2. 구현
2-1.GraphQl 쿼리
책 정보를 검색하는 검색어
limit와 offset은 인수로 적절히 지정할 수 있다.
getBooks.jsconst GET_BOOKS = gql`
query getBooks(
$limit_number: Int
$offset_number: Int
) {
books(
limit: $limit_number
offset: $offset_number
order_by: { updated_at: desc }
) {
title
isbn
author
image_path
price
publish_date
}
}
`;
취득 데이터의 이미지. 뭐라고 말할 수 없는 책의 선택.
2-2.state 관리 및 useQuery 설정
로드할 때마다 12건씩 취득하는 이미지
BookList.jsconst BookLists = React() => {
//取得したデータを格納
const [bookData, setBookData] = useState([]);
//オフセット数値を格納
const [offset, setOffset] = useState(0);
//スクロール後のデータフェッチを続けるかの判定を格納
const [hasMore, setHasMore] = useState(true);
//fetchMore取得
const { fetchMore } = useQuery(GET_BOOKS,
onCompleted: (data) => {
if (!data.books.length) {
const { books } = data.books;
setBookData(books);
}
},
};
//infiniteScrollのnextで呼ぶ関数を定義
const getBooksData = async () => {
//呼ぶ度にoffsetに12加算
setOffset((prevOffset) => prevOffset + 12);
const { data } = await fetchMore({
variables: { limit_number: 12, offset_number: offset },
});
// 取得したdata件数が0の時はhasMoreをfalseにして、スクロールを終了
data.books.length === 0 && setHasMore(!hasMore);
setBookData((prev) => [...prev, ...data.books]);
};
//初回画面表示に使用
useEffect(() => {
getBooksData();
}, []);
return (
<div>
{bookData && bookData.length > 0 && (
<BookContents
data={bookData}
nextFunc={() => getBooksData()}
hasMore={hasMore}
/>
)}
</div>
2-3.InfiniteScroll의 컴포넌트 설정
2-2에서 설정한 내용을 InfiniteScroll로 적절히 호출한다.
InfiniteBooks.jsconst InfiniteBooks = (props) => {
return (
<InfiniteScroll
//InfiniteScroll直下の要素配置を変えたい時に指定
style={{ display: 'flex', flexWrap: 'wrap' }}
dataLength={booksData.length}
next={() => props.nextFunc()}
hasMore={props.hasMore}
//material-uiのローダーを入れてる
loader={<CircularProgress />}
endMessage={
<h1 style={{ textAlign: 'center' }}>
<b>You have seen it all</b>
</h1>
}
>
{booksData &&
booksData.map((v) =>
<BooksChild key={v.title} props={v} />)}
</InfiniteScroll>
);
};
3. 참고
↓ 참고로 한 code sandbox
릭 앤 모티라는 애니메이션 캐릭터를 늘 스크롤 할 수 있습니다.
Reference
이 문제에 관하여(React + GraphQL + Apollo로 무한 스크롤 (Offset-based)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://qiita.com/sinno999/items/9fdee6ccae40435999bf
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
"react": "^17.0.2
Hasura v1.3.3
<InfiniteScroll
//表示する子要素の数(スクロールする度に増やす必要がある)
dataLength={items.length}
//ページ下部に到達した時に実行する関数を指定
next={fetchData}
//データ取得がを続けるかどうかを指定。
//trueだとページ下部に到達した時にnextで指定した関数が実行さる。
//falseだと実行されず、endMessageが表示される。
hasMore={true}
//nextの関数結果が返ってくるまで表示するう要素を指定
loader={<h4>Loading...</h4>}
//hasMoreがfalseの時に表示する要素を指定
endMessage={<p>You have seen it all</p>}
>
//スクロールさせたい要素を列挙
{sampleData.map((v) =>
<sampleDataChild key={v.key}/>)}
</InfiniteScroll>
2-1.GraphQl 쿼리
책 정보를 검색하는 검색어
limit와 offset은 인수로 적절히 지정할 수 있다.
getBooks.js
const GET_BOOKS = gql`
query getBooks(
$limit_number: Int
$offset_number: Int
) {
books(
limit: $limit_number
offset: $offset_number
order_by: { updated_at: desc }
) {
title
isbn
author
image_path
price
publish_date
}
}
`;
취득 데이터의 이미지. 뭐라고 말할 수 없는 책의 선택.
2-2.state 관리 및 useQuery 설정
로드할 때마다 12건씩 취득하는 이미지
BookList.js
const BookLists = React() => {
//取得したデータを格納
const [bookData, setBookData] = useState([]);
//オフセット数値を格納
const [offset, setOffset] = useState(0);
//スクロール後のデータフェッチを続けるかの判定を格納
const [hasMore, setHasMore] = useState(true);
//fetchMore取得
const { fetchMore } = useQuery(GET_BOOKS,
onCompleted: (data) => {
if (!data.books.length) {
const { books } = data.books;
setBookData(books);
}
},
};
//infiniteScrollのnextで呼ぶ関数を定義
const getBooksData = async () => {
//呼ぶ度にoffsetに12加算
setOffset((prevOffset) => prevOffset + 12);
const { data } = await fetchMore({
variables: { limit_number: 12, offset_number: offset },
});
// 取得したdata件数が0の時はhasMoreをfalseにして、スクロールを終了
data.books.length === 0 && setHasMore(!hasMore);
setBookData((prev) => [...prev, ...data.books]);
};
//初回画面表示に使用
useEffect(() => {
getBooksData();
}, []);
return (
<div>
{bookData && bookData.length > 0 && (
<BookContents
data={bookData}
nextFunc={() => getBooksData()}
hasMore={hasMore}
/>
)}
</div>
2-3.InfiniteScroll의 컴포넌트 설정
2-2에서 설정한 내용을 InfiniteScroll로 적절히 호출한다.
InfiniteBooks.js
const InfiniteBooks = (props) => {
return (
<InfiniteScroll
//InfiniteScroll直下の要素配置を変えたい時に指定
style={{ display: 'flex', flexWrap: 'wrap' }}
dataLength={booksData.length}
next={() => props.nextFunc()}
hasMore={props.hasMore}
//material-uiのローダーを入れてる
loader={<CircularProgress />}
endMessage={
<h1 style={{ textAlign: 'center' }}>
<b>You have seen it all</b>
</h1>
}
>
{booksData &&
booksData.map((v) =>
<BooksChild key={v.title} props={v} />)}
</InfiniteScroll>
);
};
3. 참고
↓ 참고로 한 code sandbox
릭 앤 모티라는 애니메이션 캐릭터를 늘 스크롤 할 수 있습니다.
Reference
이 문제에 관하여(React + GraphQL + Apollo로 무한 스크롤 (Offset-based)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://qiita.com/sinno999/items/9fdee6ccae40435999bf
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
Reference
이 문제에 관하여(React + GraphQL + Apollo로 무한 스크롤 (Offset-based)), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://qiita.com/sinno999/items/9fdee6ccae40435999bf텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)