본문 바로가기
TECH

검색 파라미터 🔍

by Stella-Park 2025. 12. 15.
728x90

검색 파라미터는 사실상 UI 상태를 직렬화한 표현이다.

예를 들어, 사용자가 상품 목록을 보고 있을 때

  • 필터: filter=active
  • 정렬: sort=asc
  • 페이지네이션: page=2

이 값들이 URL에 들어가면 아래와 같은 장점이 생긴다.

  • 브라우저 히스토리 지원: 뒤/앞으로 가기 시 동일한 상태를 복원할 수 있다.
  • 딥링크 가능: 특정 상태를 URL로 공유하면 다른 사용자가 동일한 화면을 볼 수 있다.
  • 공유 가능: URL을 복사하여 다른 사용자에게 보내면 동일한 필터와 정렬 상태가 유지된다.

 

왜 대부분의 앱은 문자열 파싱에 의존할까?

window.location.search나 URLSearchParams로 접근하는 방식이 대부분이다.

이 방식은 단순하지만 한계/문제가 있다.

 

타입 안전성 부족

모든 값이 문자열로 들어오기 때문에 숫자나 boolean은 직접 변환해야 한다. 때문에 변환 로직을 매번 작성해야 하고, 실수하면 런타임 오류가 발생한다.

 

중복 로직

각 컴포넌트에서 파라미터를 파싱하고 검증하는 코드가 반복된다. 예를 들면, 필터 컴포넌트, 정렬 컴포넌트, 페이지네이션 컴포넌트가 각각 URLSearchParams를 파싱해야 한다.

 

스키마 불일치

어떤 컴포넌트는 sort를 'asc' | 'desc'로 기대하지만, 다른 컴포넌트는 'asc' | 'desc' | 'random'을 허용할 수도 있다. 때문에 타입 드리프트 발생되면서 버그로 이어질 수 있다.

 

취약한 유틸리티

대부분의 앱은 간단한 헬퍼 함수로 파라미터를 직렬화/파싱한다. 하지만 배열, 중첩 객체, 복잡한 타입을 지원하지 못한다. 예: tags=["react","typescript"] → tags=react,typescript (단순 콤마 구분)

 

 

결과적으로 생기는 문제

  • 런타임 오류: 잘못된 타입 변환, 누락된 기본값
  • 예측 불가능한 버그: 서로 다른 컴포넌트가 다른 스키마를 사용
  • 유지보수 어려움: 라우트 변경 시 관련된 모든 파싱 로직을 찾아 수정해야 함
  • 테스트 복잡성 증가: 각 컴포넌트마다 파라미터 처리 로직이 분산되어 있음

 

해결책

검색 파라미터를 라우트 스코프에서 타입 안전하게 관리하기

 

TanStack Router의 접근

TanStack Router는 검색 파라미터 스키마를 라우트 정의에 내재화한다.

즉, 각 라우트가 자신이 허용하는 검색 파라미터를 명시적으로 선언하고, 타입 추론, 유효성 검증, 기본값 관리, 반응형 렌더링을 제공한다.

 

 

728x90

 

 

코드 예제 (React + TanStack Router + Zod)

 

1. 라우트 정의에서 검색 파라미터 스키마 선언

import { createFileRoute } from '@tanstack/react-router';
import { z } from 'zod';

// /products 라우트 정의
export const Route = createFileRoute('/products')({
  validateSearch: z.object({
    sort: z.enum(['asc', 'desc']).default('asc'),
    page: z.number().default(1),
    filter: z.string().optional(),
  }),
});
  • 단일 진실 공급원 (SSOT) -> 스키마가 라우트에 포함되므로 중복 없음
  • 기본값 (default)도 스키마에서 관리 -> 누락된 값 자동 처리

 

2. 타입 안전한 네비게이션

import { Link, useNavigate } from '@tanstack/react-router';

function ProductList() {
  const navigate = useNavigate();
  return (
   <>
     // ✅ 타입 안전
     <Link to="/products" search={{ sort: 'desc', page: 2 }}>Sort Desc</Link>
     <button onClick={() => navigate({
       search: prev => ({ ...prev, page: prev.page + 1 }), // ✅ 리듀서 스타일
      })
     }>
       Next Page
     </button>
   </>
  );
}
  • sort 'asc' | 'desc' 외 값 불가 → 컴파일 타임에서 오류 방지
  • page는 숫자 타입 보장
  • 상태 업데이트는 리듀서 스타일로 안전하게 처리

3. 계층적 확장 (부모 -> 자식)

// 부모 라우트
export const Route = createFileRoute('/dashboard')({
  validateSearch: z.object({
    sort: z.enum(['asc', 'desc']).default('asc'),
  }),
});

// 자식 라우트
export const Route = createFileRoute('/dashboard/$id')({
  validateSearch: z.object({
    filter: z.string().optional(),
    // ✅ sort는 부모에서 상속됨
  }),
});
  • 부모 스키마를 자식이 확장 가능
  • 잘못된 타입 재정의 시 → 컴파일 오류 발생
  • 자식 라우트에서 상속받은 파라미터를 재정의하려고 하면 타입 오류 발생

 

TanStack Router의 핵심 장점

  • 타입 안전성: 런타임 + 컴파일 타임 모두 보장
  • 단일 진실 공급원: 스키마가 라우트 정의에 포함 → 중복 제거
  • 계층적 확장: 부모-자식 라우트 간 일관성 유지
  • 반응형 렌더링: 필요한 컴포넌트만 리렌더링 → 성능 최적화

 

이상 스텔라였습니다 ✍🏻

 

 

 

 

 

 

 

728x90

'TECH' 카테고리의 다른 글

useOptimistic: React 19의 새로운 기능 🆕  (0) 2025.12.17
5가지 노드 버전 관리자 비교  (0) 2025.12.04
아이콘 현지화 (localization)  (0) 2025.12.02
논리 할당 연산자  (0) 2025.11.20
Vite+  (0) 2025.11.19