유효한 URL 파라미터 추출을 위한 useValidParams 훅 만들기(feat. useParams)

Chex·2023년 11월 19일
1

우아한테크코스

목록 보기
19/19
post-thumbnail

react-router-domuseParams를 사용하면 현재 URL에서 파라미터 값을 간편하게 추출할 수 있습니다.

만약 URL이 /users/:id 형태로 정의되어 있고, 사용자가 /users/123으로 이동한다면, useParams{ id: '123' }와 같은 객체를 반환합니다.

진행하던 프로젝트에서도 useParams 훅을 사용했었는데요.
useParams를 통해 얻는 객체의 키값이 string | undefined로 추론되어 해당 값을 가져다 쓸 때 불편함을 느꼈고 이를 해결하기 위해 useValidParams훅을 만들어 사용했습니다.

useParams 살펴보기

먼저 useParams의 반환타입을 살펴볼까요?

export declare function useParams<ParamsOrKey extends string | Record<string, string | undefined> = string>(): Readonly<[
    ParamsOrKey
] extends [string] ? Params<ParamsOrKey> : Partial<ParamsOrKey>>;

ParamsOrkey가 문자열인 경우, Params<ParamsOrKey> 타입을 반환하고 그렇지 않은 경우에는 Partial<ParamsOrKey> 타입을 반환합니다. 여기서 ParamsOrKey의 기본 타입(제네릭이 주어지지 않은 경우)은 string으로 정의되어 있습니다.

따라서 우리는 Params<ParamsOrKey>값을 더 살펴보면 되겠죠?

Params<ParamsOrKey>

export type Params<Key extends string = string> = {
    readonly [key in Key]: string | undefined;
};

ParamsOrKey가 Record<string, string | undefined> 타입인 경우, Partial<ParamsOrKey>> 타입은 모든 속성이 선택적으로 존재하는 객체임을 나타냅니다.

이에 반해, ParamsOrKey가 문자열인 경우, Params<ParamsOrKey>타입은 다른 속성들은 선택적이지만, 'ParamsOrKey'에 해당하는 속성은 필수적이라는 것을 나타냅니다.
즉, 특정 키에 해당하는 값은 string | undefined로 지정되지만 그 키 자체는 필수적이라는 뜻이죠!

  const params = useParams(); // Readonly<Params<string>>
  const params1 = useParams<string>(); // Readonly<Params<string>>
  const params2 = useParams<'petFoodId'>(); // Readonly<Params<"petFoodId">>

여기서 Params<'petFoodId'>에는 'petFoodId'라는 키가 필수이므로 이 타입은 아래와 같이 정의됩니다.

type ExampleParams = { petFoodId: string | undefined };

여기서 키에 해당하는 값이 string 또는 undefined로 추론됨을 알 수 있어요.
따라서 useParams를 통해 얻은 객체의 키 값을 가져다 쓰려면 타입가드 등 추가적인 로직이 필요합니다.

이를 해결하기 위해 useParams훅을 이용하여 URL 파라미터를 추출하고 이를 검증하는 커스텀 훅을 만들었어요!

useValidParams 만들기

import { useParams } from 'react-router-dom';

type Params<Key extends string = string> = {
  readonly [key in Key]: string;
};

export const useValidParams = <T extends string>(paramKeys: T[]): Readonly<Params<T>> => {
  const params = useParams();
  const validParams = paramKeys.reduce((acc, key) => {
    if (!(key in params)) throw new Error(`Param '${key}' not found`);
    if (!params[key]) throw new Error('Invalid Params');

    return { ...acc, [key]: params[key] };
  }, {} as Readonly<Params<T>>);

  return validParams;
};
  1. useParams 훅을 사용하여 현재 URL에서 파라미터를 추출합니다.
  2. paramKeys 배열을 순회하면서 각 키에 대해 다음 과정들을 수행합니다
    • 키가 URL 파라미터에 존재하지 않으면 에러를 던집니다.
    • 파라미터 값이 존재하지 않으면(빈 문자열 또는 falsy 값)에러를 던집니다.
    • 위 두 조건을 통과한 경우, 유효한 파라미터 객체에 현재 키와 값을 추가합니다.
  3. 주어진 paramKeys에 해당하는 파라미터만을 포함하며 각 키에 해당하는 값은 반드시 문자열이어야 하는 유효한 파라미터 객체를 반환합니다.

paramKeys 타입에 string[]이 아닌 제네릭을 사용한 이유?

paramKeys 타입에 string[]이 아닌 제네릭을 사용한 이유는 (e.g. <T extends string>(paramKeys: T[])) 구체적인 타입추론이 되도록 하기 위해서입니다.

전자의 경우 params의 타입은 Readonly<Params<string>>으로, 후자의 경우 Readonly<Params<"petFoodId">>로 추론됩니다.

따라서 전자의 경우 객체의 키 값이 추론되지 않는 반면, 후자의 경우 객체의 키 값이 구체적으로 추론됩니다.

profile
Fake It till you make It!

0개의 댓글

관련 채용 정보