React๋ก InfiniteScroll์ ํ๋ ๋ฐฉ๋ฒ 2๊ฐ์ง
- onScroll ์ด๋ฒคํธ
- Intersection Observer API
onScroll๊ณผ ๊ทธ ํ๊ณ
์ฌ์ฉ์์ scroll event๋ฅผ ๊ณ์ ๋ณด๋ฉฐ ํ์ด์ง๊ฐ ๋์ ์ค๋์ง๋ฅผ ํ๋จํ๋ ๋ฐฉ๋ฒ์ ๊ตฌํ์ด๋ค. ํ์ง๋ง
๋๋ฅด๋ฅต ์คํฌ๋กค ํ . ๋๋ง๋ค ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋ ๊ฒ์ด๋ค.
ํฌ๋กฌ์๋ค ์ด๊ฒ๋ง ์ณ๋ด๋ ์ ์ ์๋ค.
window.addEventListener('scroll',()=>console.log('๐'))
๊ทธ๋ ๋ค๋ฉด ๋จ์ ๊ฒ์
๐ฅณ Intersection Observer API
ํ๊ฒ๊ณผ viewPort ์ฌ์ด์ intersection ๋ณํ๋ฅผ ๋น๋๊ธฐ์ ์ผ๋ก ๊ด์ฐฐํ๋ ๋ฐฉ๋ฒ
- LazyLoading
- Infinite-scroll
- ์ฌ์ฉ์ ๊ฒฐ๊ณผ ์ฌ๋ถ์ ๋ฐ๋ฅธ ์ ๋๋ฉ์ด์
๋ฑ์ ์ฌ์ฉ๋๋ค.
์ฌ๊ธฐ์ ๋๋ Infinite-scroll์ ๋ง๋ค๋ฉฐ API๋ฅผ ๊ฒช์ด๋ณด์๋ค.
Infinite-Scroll ๊ตฌํ ์์
- ์คํฌ๋กคํ Component ๊ตฌ์ฑ
- Intersection observer ์์ฑ
- ์์ฑํ Intersection observer ๋ถ์ฌ์ฃผ๊ธฐ
- useFetch ์์ฑ (data๊ฐ API์์ฒญ์ผ ๋)
์คํฌ๋กคํ ๋์์ ๋ง๋ ํ
Intersection observer๋ฅผ ๋ง๋ค์ด์ ref๋ก ๋๋๋ ์ง์ ์ ๋ถ์ฌ์ค ๊ฒ์ด๋ค!
ํ์ฌ data๋ 10๊ฐ์ฉ API query ์์ฒญํ์ฌ ๋ถ๋ฌ์ค๊ณ ์๋ค.
ํ์ผ์ ํฌ๊ฒ ๋๊ฐ์ง
- useFetch(์ปค์คํ
fetch Hook)
- CardList(์คํฌ๋กคํ component)
1) ์คํฌ๋กคํ Component ๊ตฌ์ฑ
CardContainer ๊ฐ์ฅ ์๋์ ๋น div๋ฅผ ์์ฑํด ref๋ฅผ ๋ฌ์์ค๋ค.
์ด๊ณณ์์ ๊ต์ฐจ์์ ์ ์์๋ณผ ๊ฒ์ด๊ธฐ ๋๋ฌธ
<CardListContainer>
{list?.map((card) => (
<Cardd>{card.id}</Cardd>
))}
<div ref={observer} />
<>{isLoading && <Loading />}</>
</CardListContainer>
2) Intersection observer ์์ฑ (์์)
์ฐธ๊ณ ) options๋ ์ต์ ์ฌํญ์ด๋ผ ์์์ฝ๋์์๋ ๋ฐ๋ก ๋ฃ์ง ์์๋ค.
let options = {
root: document.querySelector('#scrollArea'),
rootMargin: '0px',
threshold: 1.0
}
let observer = new IntersectionObserver(callback, options);
root
: ์คํฌ๋กค ํ ๋์์ ๊ฐ์ผ ์์ , ์คํฌ๋กค ํ ๊ณณ
null ํน์ default = viewPort
_rootMargin
: root ๊ฐ ๊ฐ์ง ์ฌ๋ฐฑ
_๋ฌธ์์ด๋ก ๋ฃ๊ธฐ
threshold
: target์ด root์ ๋ช% ๊ต์ฐจํ์ ๋ callback ์ ์คํํ ์ง ๊ฒฐ์ ํ๋ ๊ฐ
0.0~1.0
3) ์์ฑํ Intersection observer ๋ถ์ฌ์ฃผ๊ธฐ
pageNum
:ํ์ด์ง๋ฒํธ๋ฅผ query ๋ก API ์์ฒญํด data๋ฅผ ๋ฐ์์ค๊ธฐ ๋๋ฌธ์ pageNum์ useFetch์ ๋๊ฒจ์คlist
: ๋ฟ๋ ค์ค dataisLoading
: ๋ก๋ฉ์ค์ธ์ง booleanhasMore
: data ๋ ๊ฐ์ ธ์ฌ ๊ฒ ๋จ์๋์ง
< CardList.jsx >
const CardList = () => {
const [pageNum, setPageNum] = useState(1);
const { list, hasMore, isLoading } = useFetch(pageNum);
const observerRef = useRef();
const observer = (node) => {
if (isLoading) return;
if (observerRef.current) observerRef.current.disconnect();
observerRef.current = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting && hasMore) {
setPageNum((page) => page + 1);
}
});
node && observerRef.current.observe(node);
};
return (
<CardListContainer>
{list?.map((card) => (
<Cardd>{card.id}</Cardd>
))}
<div ref={observer}/>
<>{isLoading && <Loading />}</>
</CardListContainer>
);
};
export default CardList;
๋ฉ์ธ์ด ๋๋ ๋ถ๋ถ๋ง ์ฃผ์์ ๋ฌ์ ์ค๋ช ํ์๋ฉด
const observerRef = useRef();
const observer = (node) => {
// 1.๋ก๋ฉ์ค์ด๋ฉด return
if (isLoading) return;
//onserverRef.current๊ฐ ์์ผ๋ฉด ๋๊ธฐ
if (observerRef.current) observerRef.current.disconnect();
//observerRef.current ์์ฑ
observerRef.current = new IntersectionObserver(([entry]) => {
// entry ๊ฐ ๊ต์ฐจ๋์์ผ๋ฉฐ, ๋ฐ์ดํฐ๊ฐ ๋ ์์ผ๋ฉด ํ์ด์ง 1์ฆ๊ฐ
if (entry.isIntersecting && hasMore) {
setPageNum((page) => page + 1);
}
});
//
node && observerRef.current.observe(node);
};
4) useFetch ์์ฑ
< useFetch.jsx>
import React, { useState, useEffect, useCallback } from 'react';
const END_POINT = 'https://';
const useFetch = (page) => {
const [list, setList] = useState([]);
const [hasMore, setHasMore] = useState(false);
const [isLoading, setIsLoading] = useState(false);
//query API ์์ฒญ ๋ณด๋ด๊ธฐ
const sendQuery = useCallback(async () => {
const URL = `${END_POINT}?${page}~~~`;
try {
setIsLoading(true);
const response = await (await fetch(URL)).json();
if (!response) {
throw new Error(`์๋ฒ์ ์ค๋ฅ๊ฐ ์์ต๋๋ค.`);
}
setList((prev) => [...new Set([...prev, ...response])]);
setHasMore(response.length > 0);
setIsLoading(false);
} catch (e) {
throw new Error(`์ค๋ฅ์
๋๋ค. ${e.message}`);
}
}, [page]);
useEffect(() => {
sendQuery();
}, [sendQuery, page]);
return { hasMore, list, isLoading };
};
export default useFetch;
// list. data ์ ์ ์๋ ๊ฐ + ์๋ก ๊ฐ์ ธ์จ query API ์๋ต๊ฐ์ ๋ํด ์๋ก์ด ๋ฐฐ์ด์ ๋ง๋ค์ด set
setList((prev) => [...prev, ...response]);
//์๋ต๊ฐ์ด ์์ผ๋ฉด set
setHasMore(response.length > 0);
// data ๋ถ๋ฌ์ค๊ธฐ ์ ์ ์ ๋ก๋ฉ์ค
setIsLoading(true);
// ์ค๋ต
setIsLoading(false);
+ ์ ๋ณด ์ถ๊ฐ
const [isLoading, setIsLoading] = useState(false);
์ด Loading state๋ ๋ก๋ฉ ๊ตฌํ์์๋ง ํ์ํ๋ค๊ณ ์๊ฐํ๋๋ฐ,
์๋์๋ค. ์ต๋ช ์ ๋ช 0๋๊ป์ ์๋ ค์ฃผ์ จ๋๋ฐ Loading State์์ด ์ฝ๋๋ฅผ ์คํํ๋ฉด data๊ฐ fetch ํ๋ ๋์ ์ต์ ๋ฒ๊ฐ useRef๋ฅผ ๊ฐ์งํ๊ณ ํ์ด์ง ์๋ฅผ ์ฆ๊ฐ์ํค๋ ํ์์ด ๋ฐ์ํ๋ค๊ณ ํฉ๋๋ค.
๋ฐ๋ผ์ loading์ปดํฌ๋ํธ๋ฅผ ๋ฐ๋ก ๊ตฌํํ์ง ์์๋ ๋ฌดํ์คํฌ๋กค์์๋ ํญ์ LoadingState๊ฐ ์์ด์ผ ํ๋ค!
๋ผ๋ ๊ฒฐ๋ก ์ ๋์ถํด์ฃผ์ จ์ต๋๋ค~!~!!!!โจ ์ด์๋ฆฌ๋ฅผ ๋น์ด 0์ง๋ ๊ฐ์ฌํฉ๋๐
๊ตฌํ ๊ฒฐ๊ณผ
๋ฌดํ ์คํฌ๋กค
๋ก๋ฉ์ค
โจ ์ฌ๊ธฐ์ ๋ณด๋์ค
useCallback
React ์์ component๊ฐ ๋ ๋๋ง ๋ ๋๋ง๋ค ๋ด๋ถ์ ์ ์ธ๋ ํํ์๋ ๋ค์ ์ ์ธ๋๋๋ฐ, ์ด๊ฒ์ ๋งค์ฐ ๋ญ๋น
์ด๊ฒ์ ๋ฐฉ์งํ๊ธฐ ์ํด useCallback์ ์ฌ์ฉํ ์ ์๋๋ฐ
1์งธ ๋ง์ดํธ ๋ ๋ ํ๋ฒ๋ง ์ ์ธํ๊ณ ๊ทธ ์ดํ๋ก ์ฌ์ฌ์ฉํ์!
- ์ด๋ฒคํธ ํธ๋ค๋ฌ ํจ์
- api๋ฅผ ์์ฒญํ๋ ํจ์์ ์ฃผ๋ก ์ฌ์ฉํ๋ค.
์ฐธ๊ณ ๋ธ๋ก๊ทธ1
์ฐธ๊ณ ๋ธ๋ก๊ทธ2
์ฐธ๊ณ ๋ธ๋ก๊ทธ3
ํธ๋๋์๋ ์ฌ๋ผ๊ฐ ^^
'๐ ํ๋ฏธ๋ ธํธ' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
input ๊ด๋ จ ์ด๋ฒคํธ ํํค์น๊ธฐ - input onChange onKeyUp onKeydown (2) | 2022.06.01 |
---|---|
ํ๋ก ํธ์๋ ์ ์ฌ 6๊ฐ์ ํ๊ณ ! (2) | 2022.04.12 |
ํฐ์คํ ๋ฆฌ ๋คํฌ๋ชจ๋ ์ ์ฉํ๊ธฐ - by ๋คํฌ๋ชจ๋ ๋ฒํผ (15) | 2022.02.03 |
ํ๋ก ํธ ์คํํฐ 2021ํ๊ณ - ์ด์จ๋ frontEND! (2) | 2022.01.10 |
ํ์ ํ๋ฉฐ ๋ง์ถฐ๋ณธ ์๊ทธ๋งํ ์ปจ๋ฒค์ ๋ชจ์ zip (4) | 2021.12.19 |
๋๊ธ