💎 React

리액트 페이지네이션 - Query Parameter

ji-hyun 2021. 12. 26. 23:29

페이지네이션 정의

 

페이지네이션은 전체 데이터를 보여주는 것이 아니라 그 버튼에 맞는 일부의 데이터를 보여주는 것... 과 같은 것

이전/다음 페이지

 

 

< 페이지네이션 정의 >
백엔드에서 가지고 있는 데이터는 많고, 그 데이터를 한 화면에 전부 보여줄 수 없는 경우에 사용됩니다. 모든 데이터를 한번에 보여줄 수 없다면 일정 길이로 끊어서 전달해야할 겁니다.
흔히 게시판의 "이전/다음 페이지"를 끊어 보여주는 기능으로 익숙할 텐데요. 많은 웹사이트에서 널리 사용되고 있는 개념이기에 친숙하게 느끼실 듯 합니다.
프론트엔드에서 현재의 위치(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

 

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