본문 바로가기
SPA/React

[React] 무한 스크롤(Infinite Scroll) 사용하기

by J4J 2021. 12. 19.
300x250
반응형

안녕하세요. J4J입니다.

 

이번 포스팅은 무한 스크롤(Infinite Scroll)을 사용하는 방법에 대해 적어보는 시간을 가져보려고 합니다.

 

 

반응형

 

 

Infinite Scroll이란?

 

Infinite Scroll은 화면에서 페이징 기법을 적용하기 위해 사용하는 방법 중 하나로 모든 데이터를 조회할 때까지 무한정 스크롤하는 기법을 의미합니다.

 

Infinite Scroll과 비교되는 가장 대표적인 것은 페이지네이션이 있습니다.

 

페이지네이션은 우리가 정말 친근하게 많이 보던 화면으로 게시판 같은 곳에 접속을 하게 되면 하단에 페이지 번호가 존재하는 페이징 기법입니다.

 

하지만 Infinite Scroll은 페이지네이션과 달리 페이지 번호가 따로 존재하지 않고 페이지네이션에서 다음 번호를 클릭해야 되는 동작을 단순히 계속 스크롤만 하는 것으로 대체해줄 수 있습니다.

 

 

 

그렇다고 Infinte Scroll은 페이지네이션의 상위 호환이다라고는 말할 수 없습니다.

 

Infinite Scroll보다 페이지네이션이 가지는 더 좋은 장점들도 있기 때문에 두 기법은 어떤 UI/UX를 사용자에게 보여줄 것인지에 따라 다르게 적용해주면 됩니다.

 

 

 

 

Infinite Scroll vs 페이지네이션

 

  Infinte Scroll 페이지네이션
목적 목적 없이 검색할 때 사용 특정 목적을 가지는 내용을 검색할 때 사용
예시 구글 이미지 검색 구글 컨텐츠 검색
다음 데이터 확인 스크롤 페이지 클릭
사용되는 데이터 스크롤을 할 수록 사용되는 데이터가 쌓임 페이지에 필요한 데이터만 존재
특정 위치 찾기 어떤 위치에 있는지 정확히 확인 불가능 페이지를 통해 위치를 바로 확인 가능
용도 모바일 친화적 PC 친화적

 

 

간단하게 다시 정리를 하면 Infinite Scroll은 이미지와 같이 목적 없이 검색을 하며 또한 모바일로 사용하는 사용자에게 적합한 페이징 기법이라고 할 수 있습니다.

 

하지만 그 외에는 페이지네이션을 사용하는 게 전반적으로 더 좋다고 여기면 될 것으로 보입니다.

 

 

 

 

구현 방법

 

Infinite Scroll을 구현하는 방법은 여러 가지가 있지만 제가 주로 사용하는 방법은 자바스크립트의 IntersectionObserver API를 활용하는 것입니다.

 

해당 API를 이용하여 스크롤의 하단에 있는 데이터를 관찰하고 있다가 화면에 보이는 순간이 오면 사용자가 다음에 확인해야 될 데이터를 로드해주게 되고 결국 무한하게 스크롤하는 UI를 제공해줄 수 있게 됩니다.

 

 

 

구현에 앞서 화면에서 불러올 데이터가 필요합니다.

 

저는 자체적으로 다음과 같이 데이터를 불러오는 서버를 만들어놨고 화면에서 해당 데이터를 계속 불러옴으로 써 Infinite Scroll을 구현해보도록 하겠습니다.

 

10개의 데이터를 불러옵니다

 

 

 

타입 스크립트 테스트 프로젝트를 하나 생성해준 뒤 App.tsx 파일에 다음과 같이 코드를 작성해보겠습니다.

 

import * as React from 'react';
import axios from 'axios';

// info interface
interface Iinfo {
    name: string;
    phone: string;
    age: number;
}

const App = (): JSX.Element => {

    // state
    const [infoArray, setInfoArray] = React.useState<Iinfo[]>([]);

    // ref
    const observerRef = React.useRef<IntersectionObserver>();
    const boxRef = React.useRef<HTMLDivElement>(null);

    // useEffect
    React.useEffect(() => {
        getInfo();
    }, [])

    React.useEffect(() => {
        observerRef.current = new IntersectionObserver(intersectionObserver); // IntersectionObserver
        boxRef.current && observerRef.current.observe(boxRef.current);
    }, [infoArray])

    // function
    const getInfo = async () => {
        const res = await axios.get('http://localhost:8080/rest/getInfo'); // 서버에서 데이터 가져오기
        setInfoArray((curInfoArray) => [...curInfoArray, ...res.data]); // state에 추가

        console.log('info data add...');
    }

    // IntersectionObserver 설정
    const intersectionObserver = (entries: IntersectionObserverEntry[], io: IntersectionObserver) => {
        entries.forEach((entry) => {
            if(entry.isIntersecting) { // 관찰하고 있는 entry가 화면에 보여지는 경우
                io.unobserve(entry.target); // entry 관찰 해제
                getInfo(); // 데이터 가져오기
            }
        })
    }

    // style
    const Wrapper = {
        width: '800px',

        margin: '0 auto'
    }

    const Box = {
        border: '1px solid olive',
        borderRadius: '8px',

        boxShadow: '1px 1px 2px olive',

        margin: '18px 0'
    }

    const BoxTable = {
        borderSpacing: '15px'
    }

    const Title = {
        fontWeight: 700
    }

    return (
        <div style={Wrapper}>
                {infoArray.map((info, index) => {
                    if(infoArray.length-5 === index) {
                        // 관찰되는 요소가 있는 html, 아래에서 5번째에 해당하는 박스를 관찰
                        return (
                            <div style={Box} ref={boxRef} key={index}>
                                <table style={BoxTable}>
                                    <tbody>
                                        <tr>
                                            <td style={Title}>이름</td>
                                            <td>{info.name}</td>
                                        </tr>
    
                                        <tr>
                                            <td style={Title}>전화번호</td>
                                            <td>{info.phone}</td>
                                        </tr>
    
                                        <tr>
                                            <td style={Title}>나이</td>
                                            <td>{info.age}</td>
                                        </tr>
                                    </tbody>
                                </table>
                            </div>
                        )
                    } else {
                        // 관찰되는 요소가 없는 html
                        return (
                            <div style={Box} key={index}>
                                <table style={BoxTable} key={index}>
                                    <tbody>
                                        <tr>
                                            <td style={Title}>이름</td>
                                            <td>{info.name}</td>
                                        </tr>
    
                                        <tr>
                                            <td style={Title}>전화번호</td>
                                            <td>{info.phone}</td>
                                        </tr>
    
                                        <tr>
                                            <td style={Title}>나이</td>
                                            <td>{info.age}</td>
                                        </tr>
                                    </tbody>
                                </table>
                            </div>
                        )
                    }
                })}
        </div>
    )
}

export default App;

 

 

 

 

실행 화면

 

위와 같이 코드를 작성하고 실행해보면 다음과 같은 결과물을 확인할 수 있습니다.

 

Infinite Scroll

 

 

 

스크롤바를 유심히 보게 되면 스크롤을 할 때마다 스크롤바의 크기가 점점 작아지는 것을 볼 수 있습니다.

 

왜냐하면 코드에서 구현해둔 대로 배열의 뒤에서 5번째에 해당하는 박스가 화면에 보이게 되면 그 순간 다음 데이터를 불러오게 되고 리 렌더링 되어 화면에 보이는 데이터의 양이 늘어나기 때문입니다.

 

결국 무한정으로 스크롤할 수 있는 상황이 만들어지게 되는 것입니다.

 

 

 

 

 

 

이상으로 무한 스크롤(Infinite Scroll)을 사용하는 방법에 대해 간단하게 알아보는 시간이었습니다.

 

읽어주셔서 감사합니다.

 

 

 

728x90
반응형

댓글