페이지네이션 정의
페이지네이션은 전체 데이터를 보여주는 것이 아니라 그 버튼에 맞는 일부의 데이터를 보여주는 것... 과 같은 것
이전/다음 페이지
< 페이지네이션 정의 >
백엔드에서 가지고 있는 데이터는 많고, 그 데이터를 한 화면에 전부 보여줄 수 없는 경우에 사용됩니다. 모든 데이터를 한번에 보여줄 수 없다면 일정 길이로 끊어서 전달해야할 겁니다.
흔히 게시판의 "이전/다음 페이지"를 끊어 보여주는 기능으로 익숙할 텐데요. 많은 웹사이트에서 널리 사용되고 있는 개념이기에 친숙하게 느끼실 듯 합니다.
프론트엔드에서 현재의 위치(Offset)과 추가로 보여줄 컨텐츠의 수(Limit)를 백엔드에 전달합니다. 백엔드에서는 그에 해당하는 데이터를 끊어 보내주는 방식으로 구현하게 됩니다.
구현 방식
프론트엔드에서 현재의 위치(Offset) 과 추가로 보여줄 컨텐츠의 수(Limit) 를 백엔드에 전달한다.
백엔드에서는 그에 해당하는 데이터를 끊어 보내주는 방식으로 구현하게 됨
Query Parameter
이 과정에서 Query Parameter(혹은, Query String, 쿼리 스트링)를 사용하게 된다. 쿼리 스트링이란 말 그대로 해당 엔드포인트에 대해 질의문(query)를 보내는 요청을 뜻한다.
예를 들어, localhost:8000/products?limit=10&offset=5 라는 주소가 있다고 가정해보자. API 뒷 부분에 붙어있는 ? 로 시작하는 텍스트가 바로 쿼리스트링 이다. 질의문 느낌..
?limit=10&offset=5 의 경우, "limit이 10이면서 offset이 5일 경우의 product 페이지를 보여달라" 는 요청으로 해석된다. (5부터 15)
<url 해석>
? : ? 기호는 쿼리스트링의 시작을 알린다. url 에서 ? 기호는 유일무이하다.
limit : 한 페이지에 보여줄 데이터 수
offset : 데이터가 시작하는 위치(index)
parameter=value 로 필요한 파라미터의 값을 적는다.
파라미터가 여러개일 경우 &를 붙여서 여러개의 파라미터를 넘길 수 있다.
여기서 offset 이런 단어는 백엔드랑 서로 합의해서 맞추는 것이다. limit=10&pageNumber=5 이런 식으로 해도 된다. 다만 백엔드와 합의한 값이라는 걸 유념하자.
useLocation().search
앞서 배운 동적 라우팅과 페이지네이션 기능의 구현 순서를 비교해보겠다.
1. 동적 라우팅
① 리스트 페이지에서 카드를 클릭한다.
② url 이동을 한다. 이때, 카드의 고유한 id 값이 url 에 포함된다.
③ 이동한 페이지에서 url 에 담겨있는 id 값을 useParams 훅을 이용하여 가져온다.
④ 가져온 id 값을 이용해서 데이터를 요청한다.
2. 페이지네이션
① 리스트 페이지에서 페이지 이동 버튼을 클릭한다.
② url 이동을 한다. 이때 url 에는 각 버튼에 해당하는 쿼리 스트링이 포함된다.
③ 이동한 페이지에서 url 에 담겨있는 쿼리스트링을 useLocation 훅을 이용하여 가져온다.
④ 가져온 쿼리스트링을 이용하여 데이터를 요청한다.
Path Parameter 에 대한 정보가 useParams 훅이 반환한 객체 안에 담겨있었듯이, 쿼리스트링에 대한 정보는 useLocation 훅이 반환한 객체의 search 프로퍼티 안에 담겨있다
// current url -> localhost:3000/products?offset=10&limit=10
function ProductList() {
const location = useLocation();
console.log(location.search) // ?offset=10&limit=10
return (
...
)
}
이를 통해 url 에서 쿼리스트링 정보를 받아와서, 해당 정보를 통해 데이터를 요청할 수 있다.
fetch(`${API}${location.search}`)
.then(res => res.json())
.then(res => ...)
무한스크롤은 어떻게 구현될까?
state 변수를 사용한다.
그리고 스크롤할때마다
?limit=10&offset=0
?limit=10&offset=10 -> 기존 state에 concat
?limit=10&offset=20 -> 기존 state에 concat
이런 식으로 구현하면 된다.
어떤 카드를 클릭했고 -> url 이 변했음
url 이 변했으면 useEffect 는 항상 감시하고 있다가 url 변하면 새롭게 데이터를 패치 받아와서 state 에 저장
state 에 저장해서 화면에 보여주게 만듦
// Users.js
export default function Users() {
const [users, setUsers] = userState([]);
// 데이터 로딩
useEffect(() => {
fetch("http://localhost:8000/users")
.then((res) => res.json())
.then((res) => setUsers(res.users));
}, []);
const updateOffset = () => {
console.log("updateOffset");
};
return (
<div className="Photos">
<h1>Mini Project - Pagination</h1>
<Buttons updateOffset={updateOffset}/> // 버튼에 클릭 이벤트 발생
<CardList users={users} />
</div>
);
}
버튼에 클릭이벤트가 발생
// Buttons.js
import React from "react";
import from "./Buttons.scss";
export default function Buttons({updateOffset}){
return (
<div className="pageBtn>
<button onClick={() => updateOffset()}>1</button>
<button onClick={() => updateOffset()}>2</button>
<button onClick={() => updateOffset()}>3</button>
<button onClick={() => updateOffset()}>4</button>
<button onClick={() => updateOffset()}>5</button>
</div>
);
이제 버튼마다 다른 쿼리스트링을 구성해야 하기 때문에 인자를 달리 전달한다.
// Buttons.js
import React from "react";
import from "./Buttons.scss";
export default function Buttons({updateOffset}){
return (
<div className="pageBtn>
<button onClick={() => updateOffset(0)}>1</button>
<button onClick={() => updateOffset(1)}>2</button>
<button onClick={() => updateOffset(2)}>3</button>
<button onClick={() => updateOffset(3)}>4</button>
<button onClick={() => updateOffset(4)}>5</button>
</div>
);
// Users.js
export default function Users() {
const [users, setUsers] = userState([]);
const navigate = useNavigate(); // 이동
// 데이터 로딩
useEffect(() => {
fetch("http://localhost:8000/users")
.then((res) => res.json())
.then((res) => setUsers(res.users));
}, []);
const updateOffset = (buttonIndex) => {
const limit = 20; // 화면에 보여줄 데이터 갯수
const offset = buttonIndex * limit; // 데이터 시작점 0 20 40 60 80..
const queryString = `limit=${limit}&offset=${offset}`;
navigate(`?${queryString}`); // 물음표의 뜻: 쿼리스트링의 시작
};
return (
<div className="Photos">
<h1>Mini Project - Pagination</h1>
<Buttons updateOffset={updateOffset}/>
<CardList users={users} />
</div>
);
}
navigate(`/pagination?${queryString}`);
이렇게 구성할 수 도 있다. 만약 /pagination 을 써주지 않으면 현재 페이지에서 자동으로 붙여진다.
동적 라우팅에서는 useEffect 에서 id 값이 useParams 훅을 사용해서 해당 id 값을 가져와서 백엔드와 통신했었다.
동적 라우팅에서는 useParams 를 사용했다면 쿼리스트링은 useLocation 을 사용한다.
// Users.js
export default function Users() {
const [users, setUsers] = userState([]);
const navigate = useNavigate(); // 이동
const location = useLocation(); // 쿼리스트링에 대한 정보
console.log(location);
// 데이터 로딩
useEffect(() => {
fetch("http://localhost:8000/users")
.then((res) => res.json())
.then((res) => setUsers(res.users));
}, []);
console.log(location) 했을 때
search 에 쿼리 스트링에 대한 정보가 들어있기 때문에 이를 이용한다.
// Users.js
export default function Users() {
const [users, setUsers] = userState([]);
const navigate = useNavigate(); // 이동
const location = useLocation(); // 쿼리스트링에 대한 정보
console.log(location);
// 데이터 로딩
useEffect(() => {
fetch(`http://localhost:8000/users${location.search}`)
.then((res) => res.json())
.then((res) => setUsers(res.users));
}, []); // 의존성배열 수정필요
쿼리스트링이 바뀔 때마다 useEffect 를 실행해야 하기 때문에 의존성 배열 안에는 [location.search] 가 들어가야 한다.
데이터 fetching 초기값/리팩토링
그러나 이대로 코드를 실행하게 되면 첫번째 페이지에는 모든 데이터가 다 보이게 되는데
그 이유는 아무것도 클릭하지 않았으므로
url 이 'localhost:3000/pagination' 이라 모든 것이 다 들어온다.
처음에는 search 값이 빈 string 값으로 들어온다.
빈 string 값은 모든 데이터가 들어온다.
컴포넌트가 먼저 mount 되고 그다음 ui 가 그려지고 그 이후에 useeffect 패칭된다.
그때 location.search 가 빈 스트링 값
useEffect(() => {
fetch(`http://localhost:8000/users${location.search || `limit=20&offset=0`}`)
.then((res) => res.json())
.then((res) => setUsers(res.users));
}, [location.search]);
|| 연산자는 && 연산자의 반대이다.
처음부분이 false 이면 뒷부분을 출력해라
처음에는 빈 스트링이므로 false
이후에 버튼 클릭하면 빈 스트링이 아니게 되기 때문에 쿼리스트링 구성됨
리팩토링 시작
limit 은 고정값 20이고 코드에서 여러 군데에 사용하고 있기 때문에 상수 변수로 만들어주는 것이 좋다
const LIMIT = 20;
사용할 부분에다가는 &{LIMIT}
그래서 템플릿 리터럴을 사용한 것
'💎 React' 카테고리의 다른 글
MakeStyle 이 뭘까 (0) | 2022.03.05 |
---|---|
React + Node Express 필터링 (0) | 2022.03.01 |
동적 라우팅 - Path Parameter 와 useParams (0) | 2021.12.19 |
styled-components (0) | 2021.12.13 |
리액트 토큰 유무에 따른 UI 변경 (0) | 2021.12.09 |