쿼리 스트링(Query String)

신주안·2022년 9월 15일

👨‍💻 쿼리 스트링(Query String)의 정의와 필요성

쿼리 스트링은 URL의 한 부분으로서, 요청하고자 하는 URL에 부가적인 정보를 포함하고 싶을 때 사용한다.
URL에 따라서 다양한 응답을 제공해주는 백엔드 API도 마찬가지로 /signin API를 호출하면 로그인에 대한 응답을, /products API를 호출하면 상품들에 대한 정보를, /product API를 호출하면 단일 상품에 대한 정보를 응답해주는 것처럼 특정 리소스에 대한 정보를 얻고자 한다는 단순한 형태의 요청만 URL을 통해서 할 수 있었다.

이런 상황에서 우리는 단순히 “상품 리스트를 보여줘”가 아니라 “상품 리스트를 최신순으로 상위 10개만 보여줘” 처럼 구체적으로 요청을 할 수 있어야 하는데 이때 활용할 수 있는 것이 쿼리 스트링입니다.

🤔 쿼리 스트링의 형태?


Query String의 형태(출처: https://www.semrush.com/)

쿼리 스트링은 이름 그대로 문자열의 형태를 띠고 있으며 key=value로 표현된다.
또한 URL의 일부분이기 때문에 여기서부터는 쿼리 스트링이 시작된다고 표시가 되어야 하며 ?를 통해서 이를 표현한다.

🎉 쿼리 스트링을 통한 라우팅

기본적인 형태

쿼리 스트링을 포함해서 Routing을 할 때도 특별히 다른 방법을 통해서 할 필요는 없다.
<Link to="/list?sort=popular" />, navigate("/list?sort=popular")처럼
단순하게 Link 컴포넌트나 navigate 함수의 인자에 쿼리 스트링이 포함된 URL을 전달해주면 된다.

컴포넌트에서 쿼리 스트링 값 가져오기

react-router-dom에서는 쿼리 스트링의 값을 편하게 가져올 수 있는 hook 들을 제공해준다.

  • useLocation
  • useSearchParams

이렇게 대표적인 두가지가 있는데 활용하기 더 좋은 useSearchParams 에 대해 알아보자

💪 useSearchParams

react-router-dom에서는 쿼리 스트링의 값을 이용해서 URLSearchParams 객체를 리턴해주는 useSearchParams라는 hook을 제공해 주고 있다.

const [searchParams, setSearchParams] = useSearchParams();
  • searchParams: URLSearchParams 객체, 쿼리스트링을 다루기 위한 여러 메소드 제공
  • setSearchParams: 인자에 객체 or 문자열을 넣어주면 URL의 쿼리스트링을 변경하는 기능 제공

searchParams의 자주 사용하는 메서드

  • 값을 읽어오는 메서드
    • searchParams.get(key): 특정한 key의 value를 가져오는 메서드
      • 가장 먼저나오는 value 를 return
    • searchParams.getAll(key) : 특정한 key에 해당하는 모든 value를 가져오는 메서드
      • 해당하는 value를 배열로 return
    • searchParmas.toString() : 쿼리 스트링을 string 형태로 리턴
      • 가공하지 않은 값을 그대로를 원할 때 사용

  • 값을 변경하는 메소드
    • seachParams.set(key, value) : 인자로 전달한 Key값을 value로 설정하는 메소드
      • 만약 동일한 key에 여러 value가 이미 존재하고 있었다면 set 메소드를 호출하면서 설정한 값 외에는 삭제된다.
    • searchParams.appned(key, value) : 기존 값을 변경하거나 삭제하지 않고 추가하는 방식으로 동작하는 메소드
      • set과 다르게 삭제하지 않고 추가하는 방식으로 동작

주의해야할점?

  • 여기서 주의 해야할 점은 searchParams를 변경하는 메소드를 사용해도 실제 url의 쿼리스트링은 변경되지않는다.
  • setSearchParams(searchParams) 로 변경해야 된다

🔥 로직 구현해보기

페이지 네이션을 만들어보자.

  • 페이지 네이션이란 콘텐츠를 여러 페이지로 나누어 다음 또는 이전 페이지로 이동하거나 특정 페이지로 이동할 수 있는 요소
  • 리스트가 많아 한 화면에 과도한 스크롤이 발생이 예상되는 경우에 적용
  • Offset: 보여줄 순서
  • Limit : 보여줄 양

그냥 요청을 하면 전체 포스트 데이터를 응답해 주지만,_start , _limit 두 개의 쿼리 스트링을 이용해서 요청을 보내면 _start에 적힌 숫자 이후의 포스트부터 _limit에 적힌 개수만큼의 포스트만 응답해 준다.

1. useSearchParams 훅을 사용하여 쿼리스트링을 searchParams 형태로 가져온다.

import React, { useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';

const List = () => {
  const [searchParams, setSearchParams] = useSearchParams();

2. searchParams.get() 메서드를 통해서 쿼리 스트링에서 offset과 limit의 값을 각각 offset, limit 변수에 저장한다.

import React, { useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';

const List = () => {
  const [searchParams, setSearchParams] = useSearchParams(); 
  const offset = searchParams.get('offset'); 
  const limit = searchParams.get('limit'); 

3. posts라는 state를 만들고 useEffet훅을 이용하여 데이터를 저장한다

그냥 fetch를 하는 것이 아니라.url에 변수를 지정해서 불어온다.
또한 의존성 배열에 offsetlimit를 넣어서 둘이 값이 변경될 때마다 useEffect가 실행되게 한다.

import React, { useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';

const List = () => {
  const [searchParams, setSearchParams] = useSearchParams();
  const offset = searchParams.get('offset');
  const limit = searchParams.get('limit');

  const [posts, setPosts] = useState([]);          

  useEffect(() => {                              
    fetch(
      `https://jsonplaceholder.typicode.com/posts?_start=${offset}&_limit=${limit}`                 
      .then((response) => response.json())
      .then((result) => setPosts(result));
  }, [offset, limit]);                             

4. posts를 map을 통해 요소들을 만들어준다.

이로써 /list?offset=0&limit=10으로 유저가 접속했을 때 첫 번째 포스트부터 10개의 포스트를 보여줄 수 있다.


import React, { useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';

const List = () => {
  const [searchParams, setSearchParams] = useSearchParams();
  const offset = searchParams.get('offset');
  const limit = searchParams.get('limit');

  const [posts, setPosts] = useState([]);     

  useEffect(() => {
    fetch(
      `https://jsonplaceholder.typicode.com/posts?_start=${offset}&_limit=${limit}`                  
    )
      .then((response) => response.json())
      .then((result) => setPosts(result));
  }, [offset, limit]);                           

  return (
    <section>
      <h1>This is Posts</h1>
      {posts.map(
        (
          { id, title }                           
        ) => (
          <article key={id}>
            <p>
              <div>id:{id}</div>
              <div>title:{title}</div>
            </p>
          </article>
        )
      )}
    </section>
  );
};

5. 버튼을 만든다.

<button>1</button>
<button>2</button>
<button>3</button>

6. 버튼을 누를때마다 offset의 값을 변경하는 함수를 만든다.

1페이지 버튼을 누르면 1~10번 포스트를, 2페이지 버튼을 누르면 11~20번 포스트를, 3페이지 버튼을 누르면 21~30번 포스트를 보여주도록 구현

  const movePage = (pageNumber) => {
    // 1
    searchParams.set('offset', (pageNumber - 1) * 10);
    setSearchParams(searchParams);
  };

        <button onClick={() => movePage(1)}>1</button>
        <button onClick={() => movePage(2)}>2</button> 
        <button onClick={() => movePage(3)}>3</button> 
profile
끝이 없네!

0개의 댓글