Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- KDT 프로그래머스 데브코스 프론트엔드
- 모던 자바스크립트 Deep Dive
- 인프런 자바스크립트 알고리즘 문제풀이
- 모던 자바스크립트 Deep Dive TIL
- 백준 js
- 모던 자바스크립트 TIL
- useRef 지역 변수
- 프로그래머스 K_Digital Training
- 우테캠 회고록
- 개발자 특강
- frontend roadmap study
- 프로그래머스 데브코스
- Frontend Roadmap
- 리팩토링 회고
- 프로그래머스 K_Digital Training 프론트엔드
- TypeScript 문법 소개
- KDT 프로그래머스
- 프로그래머스 데브코스 프론트엔드
- react 프로젝트 리팩토링
- Vue3 Router
- 프로그래머스 데브코스 프론트엔드 TIL
- 백준 node.js
- 모던 자바스크립트 딥다이브
- 투포인터알고리즘 js
- react customHook 예시
- 머쓱이
- 모던 javascript Deep Dive
- Vue3
- K_Digital Training
- useEffect return
Archives
- Today
- Total
프론트엔드 개발자의 기록 공간
[React 무한 스크롤 구현하기] (feat. typeScript, useHook) 본문
무한 스크롤은 많은 데이터를 세분화하여 필요시에 데이터를 요청해서 받아오면서 성능을 극대화한다. react+typeScript에서 무한 스크롤을 구현하는 방법을 useHook으로 관리하여 사용하는 법을 소개할 것이다.
Intersection Observer API
Intersection Observer API는 대상 요소가 상위 요소 또는 최상위 문서의 뷰포트 와 교차하는 변경 사항을 비동기식으로 관찰하는 방법을 제공합니다. 따라서 Scroll 이벤트를 걸어서 스크롤 할 때마다 함수가 실행되는 스크롤 이벤트보다 훨씬 성능이 좋다.
파일 구성
useObserver (무한스크롤 useHook)
Item (무한스크롤하는 개별 요소)
Main (Item List를 생성하는 상위 컴포넌트)
useObserver.ts
import { useEffect, useState } from 'react'
const MaxPAge = 10 // 페이지 최대 사이즈
const useObserver = () => {
const [page, setPage] = useState(1) // pagination을 위한 변수
const [isFetching, setIsFetching] = useState(true) // Loading처리를 위한 변수
const [lastIntersecting, setLastIntersecting] =
useState<HTMLDivElement | null>(null) // 구독할 타켓 정보
//observer 콜백함수
const onIntersect: IntersectionObserverCallback = (entries, observer) => {
entries.forEach((entry) => {
// 뷰 포트에 마지막 요소가 들어올 때,
if (entry.isIntersecting) {
// 페이지 최대가 넘어가지 않을 때
if (page < MaxPAge) {
// page값에 1을 더하여 새 fetch 요청을 보내게됨
setPage((prev) => prev + 1)
setIsFetching(true)
}
// 기존 타겟을 unobserve한다.
observer.unobserve(entry.target)
}
})
}
useEffect(() => {
if (!lastIntersecting) return
//observer 인스턴스를 생성한 후 구독
const observer = new IntersectionObserver(onIntersect, { threshold: 0.5 })
//observer 생성 시 observe할 target 요소는 배열의 마지막 타켓으로 지정
observer.observe(lastIntersecting)
return () => observer && observer.disconnect()
}, [lastIntersecting])
// 사용할 hook state값 내보내기
return { page, setPage, isFetching, setIsFetching, setLastIntersecting }
}
export default useObserver
1. 필요한 state 값 정의하기
- 무한 스크롤 발생 시 api 요청에 넣을 page state를 정의한다.
- 로딩 처리 UI가 필요하다면 isFetching state를 정의한다.
- 무한 스크롤을 발생시키는 조건인 타켓의 정보를 가진 lastIntersection state를 정의한다.
2. 옵저버 타켓(lastIntersection)을 의존성을 넣은 useEffect hook을 만들어준다.
- 생성자를 호출하고 임계값이 한 방향 또는 다른 방향으로 교차할 때마다 실행할 콜백 함수를 전달하여 교차 관찰자를 만듭니다. 두 번째 인자로 부가적인 옵션을 지정할 수 있습니다.
- observer.onserve(target)을 통해 관찰자를 생성한 후에는 대상 요소를 지정한다.
- 마지막으로 return을 통해 후속처리 작업을 진행해 준다. (등록한 관찰자 제거)
3. 콜백 함수를 정의한다.
- onIntersect 함수는 관찰자 대상이 지정한 임계값 (threshold)을 충족할 때 호출된다. 콜백은 IntersectionObserverEntry객체 목록과 관찰자를 수신합니다.
- 뷰 포트에 마지막 요소가 들어오면 처리할 작업을 지정한다. (여기에서는 최대 10번만 무한 스크롤 발생을 위해 10보다 작을 경우에만 페이지 요소를 증가시켜 api에 해당 page 요소를 함께 보내도록 작업)
- 마지막으로 앞에 지정했던 타켓을 관찰자 대상에서 제거한다.
Main
const Main = () => {
const [list, setList] = useState<TItem[]>([])
const { page, setPage, isFetching, setIsFetching, setLastIntersecting } =
useObserver() // 정의한 observer에서 useHook가져오기
useEffect(() => {
;(async () => {
const data = await getCharacterAPI(page)
data && setCharacterList([...list, ...data])
})()
}, [page])
return (
<>
{list?.map((data, idx) => (
<div ref={setLastIntersecting}> // 구독할 대상
<Item data={data}/>
</div>
))}
// 로딩 처리
{isFetching && (
<div style={{ textAlign: 'center' }}>
<Spinner />
</div>
)}
</>
)
}
1. List를 순회하면서 하위 컴포넌트를 생성해 주고 ref로 구독할 element를 정의한다.
2. page가 변경될 때 api 요청을 위해 useEffect hook을 이용하여 page state를 의존성으로 넣어준다.
- 기존 데이터에 새로 받아온 데이터를 누적시켜서 정의해 준다.
※ 참고
무한 스크롤은 서버의 데이터가 존재하지 않으면 무한 스크롤을 더 이상 발생시키면 안 된다.
따라서 서버에서 마지막 데이터를 알려주는 정보가 있다면 반환된 api 정보를 가지고 마지막 데이터라면 어떠한 상태를 두고 false 값으로 지정하여 더 이상 무한 스크롤을 발생시키지 않도록 한다.
const [lastPage, setLastPage] = useState(true)
...
...
if (entry.isIntersecting) {
// 페이지 최대가 넘어가지 않을 때
if (lastPage) {
// page값에 1을 더하여 새 fetch 요청을 보내게됨
setPage((prev) => prev + 1)
setIsFetching(true)
}
// 기존 타겟을 unobserve한다.
observer.unobserve(entry.target)
}
return { ..., setLastPage }
Main
useEffect(() => {
;(async () => {
const data = await getCharacterAPI(page)
if(data.last) setLastPage(false) // api에서 마지막 데이터를 반환 받은 경우
data && setCharacterList([...list, ...data])
})()
}, [page])
마지막 페이지 정보임을 나타내는 state를 하나 더 정의하여 옵저버를 통해 page state 바꾸기 전에 체크한다.
그리고 setLastPage를 함께 상태를 내보내서 Main에서 api를 통해 반환된 정보를 통해 setLastPage(false) 로 상태를 업데이트하여 사용하면 된다.
728x90
'개발지식' 카테고리의 다른 글
[FE_Roadmap] Package Managers (0) | 2023.12.09 |
---|---|
[FE_Roadmap] Web Security Knowledge (0) | 2023.11.10 |
React testing library + MSW 기본 사용법 (0) | 2022.08.15 |
아토믹 디자인(Atomic Design) 소개 및 실제 사용 경험 (0) | 2022.04.19 |
[JavaScript] 비동기 HTTP 통신 종류 (ajax, fetch,axios) (0) | 2021.08.25 |
Comments