react-router-dom
의 useParams
를 사용하면 현재 URL에서 파라미터 값을 간편하게 추출할 수 있습니다.
만약 URL이 /users/:id
형태로 정의되어 있고, 사용자가 /users/123
으로 이동한다면, useParams
는 { id: '123' }
와 같은 객체를 반환합니다.
진행하던 프로젝트에서도 useParams
훅을 사용했었는데요.
useParams
를 통해 얻는 객체의 키값이 string | undefined
로 추론되어 해당 값을 가져다 쓸 때 불편함을 느꼈고 이를 해결하기 위해 useValidParams
훅을 만들어 사용했습니다.
먼저 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 파라미터를 추출하고 이를 검증하는 커스텀 훅을 만들었어요!
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;
};
useParams
훅을 사용하여 현재 URL에서 파라미터를 추출합니다.paramKeys
배열을 순회하면서 각 키에 대해 다음 과정들을 수행합니다paramKeys
에 해당하는 파라미터만을 포함하며 각 키에 해당하는 값은 반드시 문자열이어야 하는 유효한 파라미터 객체를 반환합니다.paramKeys
타입에 string[]
이 아닌 제네릭을 사용한 이유는 (e.g. <T extends string>(paramKeys: T[])
) 구체적인 타입추론이 되도록 하기 위해서입니다.
전자의 경우 params
의 타입은 Readonly<Params<string>>
으로, 후자의 경우 Readonly<Params<"petFoodId">>
로 추론됩니다.
따라서 전자의 경우 객체의 키 값이 추론되지 않는 반면, 후자의 경우 객체의 키 값이 구체적으로 추론됩니다.