기능 구현 파이어베이스 - 페이지네이션을 구현해보자.

이유승·2023년 7월 21일
0

기능 구현

목록 보기
12/21
post-custom-banner

이전에 언급했던 이유로 인하여 파이어베이스에서 페이지네이션을 구현하려고 했을 때는 완전한 기능을 구현하기가 힘들다.

RDB와 NoSQL DB의 차이점 때문인데, 이 부분에 대해서는 나중에 내용을 더 공부하고 정리해서 따로 포스팅하려고 한다. 아무튼 지금 당장 알아야하는 내용은 파이어베이스의 쿼리 기능이 제한적이기 때문에 완전한 페이지네이션 기능을 구현하는 것이 힘들다는 것 정도.

  • 파이어베이스는 어느 지점부터 어느 지점까지의 데이터를 조회해서 가지고오라는 기능을 수행하지 못한다. 어느 지점을 기준으로 어디까지만 가지고 오라만 가능하다.



1. 어떻게 해야하는가.

최적화와 효율성을 생각하지 않는다면 DB에서 전체의 데이터를 가지고 와서 프론트단에서 페이지별로 쪼개서 렌더링할 수는 있다. 프로젝트의 규모가 작고 이용자가 적으면 생각해볼 수 있는 방법이지만, 엄청나게 비효율적이고 파이어베이스에서 권장하는 방법도 아니다.

따라서 이번에는 파이어베이스에서 권장하는 페이지네이션 구현 방법을 사용해보려고 한다.



2. 쿼리 커서로 데이터 페이지화.

Cloud Firestore의 쿼리 커서를 사용하면 쿼리에 정의한 매개 변수에 따라 쿼리에서 반환하는 데이터를 일정하게 분할할 수 있습니다.

DB에서 데이터를 목록 형태로 조회해오는 기능을 구현했을 때, 파이어베이스의 query 함수를 사용하면서 여기에 인자로 orderBy 함수나 where 함수 등을 사용했었다.

이외에도 인자로 사용할 수 있는 함수들이 있는데, 페이지네이션 구현을 위해 필요한 limit 함수와 startAt, startAfter, endAt, endBefore 함수가 바로 그것이다.

  • limit 함수.
    DB에서 데이터를 조회할 때, 몇 개의 데이터를 가져올 것인지 조건을 지정할 때 사용.

  • startAt, startAfter, endAt, endBefore 함수
    DB에서 데이터를 조회할 때, 어느 지점을 기준으로 해야하는지 오름차순/내림차순 중 어느 방향으로 데이터를 가져와야 하는지, 기준이 되는 지점을 포함해야하는지의 조건을 지정할 때 사용.

  • 위 4개 함수는 순서대로 기준점으로부터 자기자신을 포함하여 다음 데이터, 자기자신을 포함하지 않고 다음 데이터, 자기자신을 포함하여 이전 데이터, 자기자신을 포함하지 않고 이전 데이터를 가져오는 기능을 수행한다.



3. 어떻게 구현하는가?

예시를 들어 쿼리 커서를 이용한 페이지네이션 기능 동작의 과정을 설명해보겠다.

  • DB에서 10개의 데이터가 반환되었다.
  • 데이터 중 가장 첫 번째 문서와 마지막 문서를 따로 저장해둔다.
  • 다음 페이지로 넘어갔을 때, 저장된 마지막 문서를 기준으로 10개의 데이터를 반환받는다.
  • 이전 페이지로 돌아간다면 첫 번째 문서를 기준으로 10개의 데이터를 반환받도록 한다.

한 페이지에 출력하기 위해 반환받은 데이터의 양 끝점을 기준으로 삼아서 이전 페이지와 다음 페이지로 넘어가는 기능을 구현할 수 있는 것이다. 이게 파이어베이스에서 권장하는 페이지네이션의 구현 방법. 이렇다보니 파이어베이스에서 페이지네이션은 숫자 버튼을 이용해서 특정 페이지로 바로 넘어갈 수가 없다.


본인들 홈페이지에서도 지원하는 기능인데..



4. 실제 구현 방법.

내 기준으로는 DB에서 데이터를 목록 형태로 조회하는 기능에서 몇몇 함수를 추가하면 된다.

파이어베이스의 사용도 익숙하지가 않은데다가 조건문이 많아져서 내가 코딩한 코드를 보는 것도 어지럼증이 느껴지던 터라.. 페이지네이션 기능을 구현하는 것은 데이터를 목록 형태로 가져오는 것과 별개의 기능으로 구현하였다.

우선 페이지네이션에 필요한 기준점을 가져온다. 프론트 혹은 서버에서 기준점을 필요로 할 때 바로 사용이 가능하도록 첫 번째 요소와 마지막 요소를 조회하고 이들이 Redux Store에 저장하도록 구현하였다.

let firstQueryRef = '';
let LastQueryRef = '';

(...)

firstQueryRef = query( (...) limit(1));
LastQueryRef = query( (...) limitToLast(1));
        
  • limitToLast?
    startAt, startAfter, endAt, endBefore 함수와 마찬가지로 데이터베이스에서 특정 범위의 데이터를 쿼리하는데 사용하는 함수. 반환된 결과값에서 가장 마지막을 기준으로 데이터를 가져온다.

그리고 다음 페이지, 이전 페이지 기능은 Redux에 저장된 기준점을 이용하여 동작하도록 구현하였다.

queryRef = query( (...) startAfter(lastOfPage), limit(itemPerPage));
queryRef = query( (...) endBefore(firstOfPage), limit(itemPerPage));

여기까지만 하면 쿼리 커서를 이용한 페이지네이션 기능을 다 구현했...다고 생각되지만 사실 하나 더 만들어야 할 것이 있다. 페이지가 이동할 수 있는 범위는 당연하게도 전체 데이터의 길이를 초과할 수 없다. 따라서 전체 데이터의 양 끝점 이외에도 현재 데이터의 양 끝점을 따로 저장할 필요가 있는 것이다.

즉, 쿼리 커서 페이지네이션 기능에는 다음과 같은 3가지 요소가 필요하다.

1. 전체 데이터의 양 끝점.
2. 한 페이지 기준 데이터.
3. 한 페이지 기준 데이터의 양 끝점.

부가기능, 페이지 이동 버튼의 가변 UI.

페이지네이션 기능에 사용되는 UI는 이전 페이지로 이동하는 버튼과 다음 페이지로 넘어가는 버튼이 존재한다. 그런데 해당 페이지가 가장 처음이거나, 가장 마지막일 때는 더 이상 이동할 수가 없도록 UI를 달라지도록 해야 한다.

이를 위해서는 Redux Store에 저장된 전체 데이터의 양 끝점과 한 페이지 기준 데이터의 양 끝점을 비교해주어야 한다.

const [isDataFirst, setIsDataFirst] = useState(false);
const [isDataLast, setIsDataLast] = useState(false);

(...)

useEffect(() => {
    if (getStoreState.processInfo.processData1 !== '' || getStoreState.processInfo.processData2 !== '') {
        setListData(getStoreState.processInfo.processData2);

        const firstItem = getStoreState.processInfo.processData1.firstOfPage;
        const lastItem = getStoreState.processInfo.processData1.lastOfPage;
        const firstIndex = getStoreState.processInfo.processData1.firstOfAllList;
        const lastIndex = getStoreState.processInfo.processData1.lastOfAllList;

        if (firstItem?.data().number === firstIndex?.data().number) {
            setIsDataFirst(true);
        }
        else {
            setIsDataFirst(false);
        };

        if (lastItem?.data().number === lastIndex?.data().number) {
            setIsDataLast(true);
        }
        else {
            setIsDataLast(false);
        };
    };

}, [getStoreState.processInfo]);
    

useEffect Hook과 디펜던시를 이용하여 페이지가 최초 렌더링 될 때 그리고 페이지에서 렌더링 되는 요소들의 내용이 달라질 때마다 화면에 출력된 데이터가 DB 전체 기준으로 어느 지점에 있는지 비교하도록 구현하였다. 가장 첫 페이지라면..

이전 페이지 버튼이 페이지 끝 버튼으로 바뀌어서 아무 기능도 동작하지 않도록 하고..

마지막 페이지라면 다음 페이지 버튼이 바뀌게 된다. 테스트 해보니.. 기능이 잘 동작한다.

코드 평가.

평가 방법, 개인적인 코드 리뷰 및 Chat GPT 사용.

-> 코드 리팩토링 필요. 작성한 코드에서 중복되는 부분이 많고, 변수 혹은 함수명이 명확하지 않는 부분이 확인됨. 가독성과 유지보수 측면에서 코드를 한번 깔끔하게 리팩토링 할 필요가 있음.
기능 구현을 처음하는데다가 하나의 기능을 구현한 다음에 다른 기능을 추가한 탓에 코드가 뒤로 갈 수록 점점 지저분해졌다.. 서버 코드는 한번 리팩토링을 해준 것인데도 저 정도. 아예 코드를 처음부터 다시 만들어보는게 좋을 듯도 하다.

-> 최적화 작업 필요. 다량의 데이터를 주고받으며 성능 문제가 발생할 소지가 있음. 최적화하기 위해 가상화(virtualization) 기법을 사용하거나, 필요한 데이터만 렌더링하는 방법을 고려할 수 있음.

profile
프론트엔드 개발자를 준비하고 있습니다.
post-custom-banner

0개의 댓글