22.3.2 (수) +38days
무한 스크롤(Infinite Scroll)이란?
API 를 통해서 많은 양의 데이터를 가져와 화면에 렌더링해야 할 때 성능을 최적화 하기 위한 방법중 페이지네이션을 구현해 봤다.
모바일에서 페이지네이션 방식을 사용하려면 원하는 페이지로 이동하기 위해 숫자 버튼을 일일이 손으로 눌러야 하기 때문에 매우 불편하다.
이 떄, 사용할 수 있는게 무한 스크롤이다.
사용자가 스크롤링 하다가 미리 로드된 컨텐츠를 다 확인하면 다음 목록을 또 로드해서 별도의 인터랙션 없이 목록을 계속 불러오는 방식이다.
스크롤이 최하단에 왔는지 판단하기
Element.scrollHeight
: 엘리먼트의 총 높이를 나타내며 바깥으로 넘쳐서 보이지 않는 콘텐츠도 포함
Element.clientHeight
: 엘리먼트의 내부 높이 (padding 포함, scroll bar 높이, margin, border 미포함)
Element.offsetHeight
: 엘리먼트의 내부 높이 (padding 포함, scroll bar 높이, margin, border 포힘)
Element.scrollTop
: 스크롤 바의 Top 부분이 화면에 내려온 위치
즉, scrollHeight - clientHeight - scrollTop이 미리 정해높은 offset 미만 일 때 스크롤이 최하단에 왔다고 판단해서 다음 페이지를 가져오고 기존 항목들에 덧붙여(append) 주면 된다.
스크롤 방식의 한계
스크롤을 움직일 때마다 이벤트가 발생하기 때문에 성능 문제가 야기될 수 있다.
이를 해결하기 위해 보통 스크롤 이벤트에 쓰로틀링(throttle) 을 적용하여 이벤트를 제한한다.
Debounce vs Throttling
주로 DOM 이벤트를 기반으로 실행하는 자바스크립트를 성능상의 이유로 이벤트를 제한할 때 debounce와 throttling을 적용한다. 이 둘이 차이는 뭘까.
debounce
: 이벤트를 그룹핑해서 특정 시간이 지난 후 하나의 이벤트만 발생하도록 하는 기술. 연달아서 호출되는 함수들 중 마지막 함수만 호출되도록 하는 것
throttling
: 이벤트를 일정한 주기마다 발생하는 기술. 마지막 함수가 호출된 후 일정 시간이 지나기 전에 다시 호출되지 않도록 하는 기술
App.tsx
import axios from 'axios';
import React, { useEffect, useRef, useState } from 'react';
import { throttle } from 'throttle-debounce';
import './index.css';
interface Airline {
slogan: string;
head_quaters: string;
website: string;
established: string;
}
interface Passenger {
_id: string;
name: string;
trips: number;
airline: Airline;
__v: number;
}
function App() {
const listRef = useRef<HTMLUListElement>(null);
const currentPageRef = useRef<number>(0);
const [passengers, setPassengers] = useState<Array<Passenger>>([]);
const [isLast, setIsLast] = useState<boolean>(false);
const [isScrollBottom, setIsScrollBottom] = useState<boolean>(false);
const getPassengers = async (init?: boolean) => {
const params = { page: currentPageRef.current, size: 100 };
try {
const response = await axios.get(
'https://api.instantwebtools.net/v1/passenger',
{ params }
);
const isLast = response.data.totalPages === currentPageRef.current;
init
? setPassengers(response.data.data)
: setPassengers(passengers.concat(response.data.data));
// setPassengers(
// init ? response.data.data : passengers.concat(response.data.data)
// );
setIsLast(isLast);
} catch (e) {
console.error(e);
}
};
const handleScroll = throttle(1000, () => {
if (listRef.current) {
const { scrollHeight, offsetHeight, scrollTop } = listRef.current;
const offfset = 50;
console.log('trigger');
setIsScrollBottom(scrollHeight - offsetHeight - scrollTop < offfset);
}
});
useEffect(() => {
if (isScrollBottom) {
currentPageRef.current += 1;
!isLast && getPassengers();
}
}, [isScrollBottom, isLast]);
useEffect(() => {
getPassengers(true);
}, []);
// console.log(passengers.length);
return (
<div className="App">
<ul ref={listRef} className="list" onScroll={handleScroll}>
{passengers.map((passenger) => (
<li className="item" key={passenger._id}>
{passenger.name}
</li>
))}
</ul>
</div>
);
}
export default App;
본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다
댓글