구현 목표 : api를 통해 받아온 데이터를 무한스크롤로 15개씩 페이지네이션 구현
사용한 내장함수 및 Hooks
intersection observer v1
useState,useRef, useEffect
array.prototype : filter, map
Object.prototype : includes
function App() {
//api 데이터를 받아오는 state.
const [storeList, setStoreList] = useState<any>([]);
//api 데이터의 특정 단어 필터링을 위한 변수
const filterString = ["미용","숙박업","이용업","기타서비스업","세탁업"];
// api 데이터를 1페이지씩 자르기 위한 state.
// 받아온 공공데이터는 페이지와 페이지 당 데이터 갯수를 정할 수 있었다.
const [pageNumber, setPageNumber] = useState(1);
// loading 상태일 때에는 observer가 관찰하는 element를 보이지 않게 하고
// loading이 아닐 때 내용을 보여주기 위한 state
const [isLoading, setLoading] = useState(true);
// 관찰대상. nextPage의 가시성 정도에 따라 그 다음의 api 데이터를 불러올 수 있게 만들었다.
const nextPage = useRef<HTMLDivElement>(null);
// fetch 함수.
const fetchGoodStore = async (pageNumber:number) => {
const res = await fetch(
`https://fakedomain.com/api/page=${pageNumber}&perPage=15&serviceKey=T^Hdg3Z`)
const data = await res.json();
// StoreList state에 저장할 데이터.
// 사용을 원치 않는 업종을 필터링하기 위해 단어들을 모아
// filterString 배열을 만들었다.
setStoreList((prev:any) =>
[...prev,data.data.filter(({업종}:any) => !filterString.includes(업종))] )
// 데이터를 다 받고 loading을 false로 처리하여 화면에 rendering 되게 함.
setLoading(false);
}
// useEffect를 통해 pageNumber가 바뀔때마다 새로운 data가 rendering이 되게 했다.
useEffect(()=>{
fetchGoodStore(pageNumber);
},[pageNumber])
//페이지를 1씩 증가시키는 함수.
const pageNumberIncrease = () => {
setPageNumber((prevPage) => prevPage + 1)
}
// 주인공 intersection observer가 사용하는 callbackFunction이다.
// 이 함수를 통해 관찰 대상이 포착되면 어떤 행동을 할 것인지 정의한다.
// entry를 default값으로 사용할 것이므로 특별히 커스텀할것은 없었다.
// 잊어먹었으면 공식문서를 보자.
const onIntersect: IntersectionObserverCallback = (entries,observer) =>{
entries.forEach((entry) =>{
if(entry.isIntersecting){
// 대상이 관찰된다면 isLoading을 true로 주었다.
// 그 이유는 컴포넌트에 삼항연산자를 통해 대상을 감추기 위함이다.
setLoading(true);
// 관찰되면 페이지를 1씩 늘린다.
pageNumberIncrease();
// 여기에다가 fetch 함수를 사용하면 안된다. 잊지말자.
// useEffect의 의존성배열에 pageNumber를 넣었다.
// 바뀔때마다 render 되게 했으니 넣지말자.
// 여기에 또 넣으면 두번실행되어 중복 데이터가 들어간다.
// 생각하면서 코딩하자
// >>>>>>> fetchGoodStore(pageNumber) <<<<<< 주의 주의 주의
// 관찰이 끝나면 관찰 대상을 unobserve 한다.
observer.unobserve(entry.target);
}
})
}
useEffect(() => {
// 관찰 대상이 보이지 않으면 실행되지 않게 한다.
if(!nextPage.current) return;
// 주인공 I.O threshold는 가시성의 정도다. 1.0 = 100% 보여야 한다.
const observer:IntersectionObserver = new IntersectionObserver(onIntersect,{threshold:[1.0]});
observer.observe(nextPage.current as Element)
// 관찰 이후 꼭 소멸시켜주자.
return () => observer && observer.disconnect();
})
return (
<>
<TopNavigation />
<Containner>
// isLoading이 false라면 해당 컴포넌트가 보인다.
{!isLoading ?
storeList.map((storeArray:any) => storeArray.map((store:any) =>
<Card /> :"Loading...."}
// isLoading이 false라면 해당 컴포넌트가 보인다.
{!isLoading ? <div ref={nextPage} style={{ width: '100%', height: '200px' }} /> : <div style={{ width: '100%', height: '200px' }}>"Loading" </div>}
</Containner>
</>
);
}
export default App;
Intersection Observer를 통해 Infinite Scroll을 구현하며 얻은 점.
1. useEffect에 대해 알게 되었다. 특히 의존성 배열에 값이 있는지,없는지, 아니면 의존성 배열 자체가 없는지에 따라 어떻게 움직이는지 이해할 수 있었다.
2. data를 불러오고 가공할 수 있는 스킬이 향상되었다. 무지성 map이 아니라, map을 사용하면 return값으로 새로운 배열을 반환받는다는 의미를 알게 되었고, filter를 사용하면 함수 블록 내 조건에 true인 값으로 새로운 배열을 만들어 반환한다는 의미를 알게 되었다. 아직 한참 부족하지만 이번 무한 스크롤 구현으로 배열이라는 자료형에 이해를 더할 수 있었다.
3. fetch 함수. 잘 알고 있다고 생각했는데, 처음에는 호출도 하지 않고 왜 데이터를 못 받아 오나 생각했다. 이제는 데이터를 api로 받는 과정까지는 문제 없이 진행할 수 있을 것 같다.
(cors가 없다면...)4. console의 활용. 콘솔은 에러를 보여주는 것 뿐만 아니라 이후 어떻게 되는지까지도 이해하게 만들어주는 말 그대로 '개발자도구'다.
공부가 필요한 부분
1. rendering이 어떻게 일어나는지 확실한 이해가 필요하다. rendering이 어떻게 일어나는지 모르니 바로 앞에 있는 해결책을 잡지 못하고 다른 곳에서 답을 찾는 경우가 많았다.
2. typescript. 이부분은 계속 경험하며 배울 수 있을 것 같다.
더 공부하면서 느낀 점
1. IntersectionObserver를 사용할 때 rerendering이 되는 이유?
obsever나 기타 state의 결과로 삼항연산자를 JSX 내에 사용하면 rerendering이 된다.