이전 글에서 페이지네이션을 구현하려고 했습니다. 제가 백엔드로 사용하고 있는 firebase 에는 커서 페이지네이션 기능만 지원하고 있고 오프셋 페이지네이션 기능은 지원하지 않고 직접 구현하기로 했고 처음 화면에 컴포넌트가 마운트 될 때 서버에서 전체 데이터를 가지고 와서 페이지네이션 네비게이션 UI 를 구현하고 8개의 데이터만 화면에 보여주는 것 까지 구현하였습니다.
오늘은 페이지네이션 네비게이션 UI 를 클릭해 클릭한 UI 에 해당하는 데이터를 서버에서 불러와 화면에 보여주는 것까지 구현하도록 하겠습니다.
저번 시간까지 구현한 페이지네이션 네비게이션 UI 입니다.
저는 양쪽의 화살표 UI 를 클릭하면 페이지가 이동해 해당 페이지에 맞는 데이터를 서버에서 불러와 화면에 보여주도록 구현하였고 사용자가 지금 어떤 페이지를 보고 있는지 알 수 있도록 현재 페이지에 해당하는 숫자 UI 에 하늘색으로 표시를 하였습니다.
페이지네이션 기능을 구현하는 이유중에 하나가 모든 데이터를 서버에 요청하지 않고 현재 필요한 데이터만 서버에 요청해 네트워크 낭비와 빠른 응답 속도로 사용자에게 빠르게 데이터를 보여주기 위한 것 입니다.
저는 먼저 화살표 UI 를 클릭했을때 기능을 구현 하였습니다.
왼쪽, 오른쪽 화살표 UI 를 클릭했을때 호출되는 콜백 함수에 숫자를 인자로 넘겨주어 콜백 함수 내부에서 넘겨받은 인자의 값에 따라서 어떤 화살표를 클릭 했는지 구분하여 이에 해당하는 데이터를 서버에 요청하여 받는 로직을 설계 하였습니다.
그럼 코드로 보시겠습니다.
<HomeStyle.PrevBtn
onClick={() => onclickPageHandler(1)}
clickDisable={
postData &&
currentData &&
postData[0].createTime !== currentData[0].createTime
? true
: false
}
>
<span class="material-symbols-outlined">chevron_left</span>
</HomeStyle.PrevBtn>
/// 숫자 UI 코드
<HomeStyle.NextBtn
onClick={() => onclickPageHandler(2)}
clickDisable={currentData.length < 8 ? false : true}
>
<span class="material-symbols-outlined">chevron_right</span>
</HomeStyle.NextBtn>
위의 코드는 화살표 UI 의 jsx 코드 입니다. 숫자 코드는 생략 했습니다.
보시면 onClick 이벤트가 발생하면 두 컴포넌트는 onclickPageHandler 라는 콜백 함수를 호출 합니다.
PrevBtn 컴포넌트는 왼쪽 화살표이며 콜백함수에 1을 넘겨주었습니다.
NextBtn 컴포넌트는 오른쪽 화살표이며 콜백함수에 2를 넘겨주었습니다.
저는 콜백함수 로직에서 숫자로 구분하고 싶어서 이렇게 한 것이기 때문에 개발자가 원하는대로 어떤식으로 왼쪽, 오른쪽을 구분할지는 자유 일 것 입니다.
그럼 이제 콜백 함수의 코드를 보시겠습니다.
먼저 저는 화살표를 클릭하면 호출되는 콜백함수에서 매개변수로 mode 를 받았습니다. 이 mode 로 사용자가 어떤 화살표를 클릭했는지 구분 할 것 입니다.
let q = query(
collection(dbService, "test"),
orderBy("createTime", "desc"), // createTime 기준으로 내림차순으로 정렬
limit(8),
mode === 1
? endBefore(currentData[0].createTime)
: startAfter(currentData[currentData.length - 1].createTime)
);
const querySnapshot = await getDocs(q);
const postData = [];
querySnapshot.forEach((doc) => {
postData.push({ id: doc.id, ...doc.data() });
});
mode 의 값에 따라서 다른 쿼리문을 생성 합니다.
쿼리문에서 mode 값에 따라 달라지는 부분은 범위를 지정하는 부분 입니다.
공통되는 부분은 모든 데이터의 createTime 을 기준으로 내림차순 하는 것과 서버로부터 받는 데이터의 개수는 8개로 제한하는 것 입니다.
다른 부분은 서버로부터 받는 데이터의 범위 입니다.
왼쪽 화살표는 위의 공통되는 부분인 createTime 을 기준으로 내림차순한 데이터에서 현재 currentData 의 첫번째 요소의 createTime 이 끝나는 점으로하고 해당 요소를 포함하지 않는다는 범위를 나타내는 함수인 endBefore 을 사용 합니다.
오른쪽 화살표는 위의 공통되는 부분인 createTime 을 기준으로 내림차순한 데이터에서 현재 currentData 의 마지막 요소인 createTime 이 시작하는 점으로하고 해당 요소를 포함하지 않는다는 범위를 나타내는 함수인 startAfter 을 사용 합니다.
이제 쿼리문을 만들었으니 데이터를 서버에 요청해 다시 currentData 를 업데이트 해주면 됩니다.
전체 코드를 보시겠습니다.
// 현재 화면에 보여지는 데이터를 저장하는 state
const [currentData, setCurrentData] = useState([]);
// 화살표를 클릭하면 호출되는 콜백 함수
const onclickPageHandler = async (mode) => {
try {
let q = query(
collection(dbService, "test"),
orderBy("createTime", "desc"), // createTime 기준으로 내림차순으로 정렬
limit(8),
mode === 1
? endBefore(currentData[0].createTime)
: startAfter(currentData[currentData.length - 1].createTime)
);
const querySnapshot = await getDocs(q);
const postData = [];
querySnapshot.forEach((doc) => {
postData.push({ id: doc.id, ...doc.data() });
});
setCurrentData(postData);
} catch (e) {
console.log(e);
}
};
위의 코드를 보시면 화살표를 클릭 했을때 호출되는 콜백함수는 위와 같이 작성 할 수 있습니다.
이제 페이지네이션 구현의 마지막 과정까지 왔습니다.
숫자 UI 를 클릭했을때 숫자에 해당하는 데이터가 화면에 보여지는 기능을 구현 할 것 입니다.
여기서 컴포넌트가 처음 마운트 될때 서버에서 전체 데이터를 요청해 전체 데이터를 저장했던 postData state 가 사용 됩니다.
해당 state 가 페이지네이션 네비게이션 UI 를 구현하는데도 사용되었죠.
숫자 UI 를 클릭하였을 때 전체 데이터 state 가 필요한 이유는 화살표 UI 처럼 현재 페이지에서 왼쪽, 오른쪽으로 데이터의 범위가 지정되는 경우도 있지만 예를 들어 3 페이지에서 1페이지로 이동할수도 있기 때문입니다. 이렇게 되면 현재 화면에 보여주고 있는 데이터를 관리하는 currentData state 를 이용해서 다음 화면에 보여 줄 데이터의 범위를 지정해 쿼리문을 생성하는데 제한이 있습니다.
저는 이 문제를 해결하기 위해 전체 데이터를 관리하는 postData state 를 이용하였습니다.
그 아이디어는 숫자 UI 에서 클릭하여 호출되는 콜백함수에 어떤 페이지를 클릭하였는지 정보를 인자로 넘겨주고 콜백함수에서 쿼리문에 클릭한 페이지에 해당하는 범위를 지정하여 생성하는 것 입니다.
코드로 보시죠.
{postData &&
new Array(Math.ceil(postData.length / 8)).fill().map((item, index) => (
<HomeStyle.PageNumberBtn
onClick={() => onclickPageNumber(index)}
currentPage={findCurrentPage() === index}
>
{index + 1}
</HomeStyle.PageNumberBtn>
))}
위의 코드는 숫자 UI 를 생성하는 jsx 코드 입니다. 숫자 UI 를 클릭하면 클릭한 숫자 UI 에 해당하는 인덱스를 인자로 넘겨 주는 것 입니다.
화면에 보여지는 숫자는 1 페이지 부터 화면에 보여져야 하니까 index + 1 로 보여주지만 콜백함수에 넘겨 줄때는 index 값 그 자체로 넘겨 줍니다.
그 이유는 전체 데이터에서는 8개씩 잘라서 화면에 보여주는 것인데 예를 들어 1 페이지는 전체 데이터에서 0번째 인덱스부터 8개의 데이터이기 때문입니다.
그럼 콜백함수의 코드를 보시겠습니다.
let q = query(
collection(dbService, "test"),
orderBy("createTime", "desc"), // createTime 기준으로 내림차순으로 정렬
limit(8),
startAt(postData[pageNumber * 8].createTime)
);
먼저 쿼리문을 생성합니다. 위에서 제가 설명 드렸듯이 콜백함수에게 넘겨 받는 페이지 숫자를 이용해 서버에 요청할 데이터의 범위를 지정 합니다.
나머지 쿼리 함수는 지금까지 쿼리문을 생성할때와 같습니다.
모든 데이터의 createTime 값을 기준으로 내림차순으로 정렬 후 8개의 데이터를 서버에 요청 합니다.
그때 데이터의 범위는 startAt을 이용하여 전체 데이터의 postData 의 매개변수로 전달받은 pageNumber 값에서 곱하기 8 를 한 값의 인덱스에 해당하는 요소의 createTime 부터로 지정 합니다.
pageNumber 8 을 한 이유는 8개의 데이터가 하나의 페이지를 구성하기 때문에 예를 들어 pageNumber 의 값이 0 이라면 0 8 은 0 이니까 postData 의 0 번 인덱스 요소의 createTime 부터 범위를 지정할 수 있고 이 해당 쿼리로 서버에 요청하면 1 번째 페이지에 해당하는 8개의 데이터를 서버에게 응답 받을 수 있는 것 입니다.
그럼 전체 코드로 보시겠습니다.
// 현재 화면에 보여주는 데이터 state
const [currentData, setCurrentData] = useState([]);
// 전체 데이터 state
const [postData, setPostData] = useState(null);
// 숫자 UI 를 클릭했을 때 호출되는 콜백함수
const onclickPageNumber = async (pageNumber) => {
try {
let q = query(
collection(dbService, "test"),
orderBy("createTime", "desc"), // createTime 기준으로 내림차순으로 정렬
limit(8),
startAt(postData[pageNumber * 8].createTime)
);
const querySnapshot = await getDocs(q);
const data = [];
querySnapshot.forEach((doc) => {
data.push({ id: doc.id, ...doc.data() });
});
setCurrentData(data);
} catch (e) {
console.log(e);
}
};
위의 쿼리문으로 서버에 요청하고 응답받은 데이터를 currentData 에 업데이트 해주면 사용자가 클릭한 숫자 UI 에 해당하는 데이터를 화면에 보여 줄 수 있습니다.
페이지네이션을 구현하는 가장 좋은 방법은 백엔드와 잘 협업하여 백엔드에서 구현한 페이지에 해당하는 데이터를 요청만해서 받아와 화면에 보여주는 것이 제일 좋은 방법일 것 입니다.
그러나 현재 환경에서 이용할 수 있는 기능이 없다면 직접 만들어 보는것도 좋은 경험이라고 생각 합니다.
위와 같이 구현하면 처음 화면에 컴포넌트가 마운트 할때만 서버에서 전체 데이터를 요청하고 그 이후부터는 화면에 보여줄 8개의 데이터만 요청 할 수 있게 됩니다.