react-router
SPA을 위해 React의 Client-side routing을 위해 필요한 녀석이다.
Declarative routing for React apps at any scale | React Router
Version 6 of React Router is here! React Router v6 takes the best features from v3, v5, and its sister project, Reach Router, in our smallest and most powerful package yet.
reactrouter.com
대표적으로 BrowserRouter, Switch, Route 그리고 Link 정도를 import 해서 자주 쓴다.
오늘은 이 중에서 Route와 관련하여, 함께 쓸 수 있는 useParams 를 살펴보려 한다.
useParams는 Route 컴포넌트에서 path에 정의된 param의 이름에 req.param 을 매핑해주는 녀석이다.
아래와 같은 코드를 가정하면,
useParams()를 통해, Route 컴포넌트에서 path에 정의된 catId의 실제 req.param값을 Object로 받아올 수 있다.
// 'src/Router.tsx'
import { BrowserRouter, Switch, Route } from "react-router-dom";
import Cat from "./routes/Cat";
import Dog from "./routes/Dog";
function Router() {
return (
<BrowserRouter>
<Switch>
<Route path="/cat/:catId">
<Cat />
</Route>
<Route path="/dog/:dogID">
<Dog />
</Route>
</Switch>
</BrowserRouter>
);
}
export default Router;
// 'src/routes/Cat.tsx'
import { useParams } from "react-router";
interface RouteParams {
catId: string;
}
function Cat() {
const { catId } = useParams<RouteParams>();
console.log(useParams<RouteParams>())
return <h1>Cat: {catId}</h1>;
}
export default Cat;
이 때, useParams를 뜯어보면 아래와 같은 코드를 볼 수 있다.
export function useParams<Params extends { [K in keyof Params]?: string } = {}>(): Params;
위 코드를 이해하기 위해서 먼저 알아야할 것은 keyof 의 의미이다. keyof는 말그대로 어떤 Type이 지니고 있는 key 그 자체를 의미한다.
interface Person {
gender: number;
name: string;
}
type PersonKeys = keyof Person; // "gender" | "name"
자 그럼 in keyof 를 알아보자.
A in keyof B는 B라는 Type에 존재하는 Key 각각을 A로 받아오는 것을 의미한다. 이러한 in keyof는 사실 Partial<T> 라는 official utility type 의 구현에 쓰이고 있다. 이를 좀 더 알아보자.
interface God {
name: string;
gender: number;
location: string;
}
interface PartialGod {
name?: string;
location?: string;
}
const demiGod:PartialGod = {
name : 'Jupyter'
}
위와 같이, 이미 존재하는 interface God의 key를 optional로 처리하고 싶은 경우, 아래와 같이 작성할 수 있다.
interface God {
name: string;
gender: number;
location: string;
}
type PartialGod = Partial<God>;
const demiGod : PartialGod = {
name:'Jupyter'
}
extends나 implements가 기존 interface를 그대로 받아와서 뭔가를 덧대는 느낌의 확장이라면, 위의 Partial은 기존의 인터페이스를 optional로 처리해주는 녀석이다. 근데 이때, Partial에 쓰이는 녀석이 바로 in keyof 이다.
type Partial<T> = {
[P in keyof T]?: T[P];
};
위 코드를 해석하자면, Partial<T> 라는 타입을 하나 선언하는데, T는 Generic 이므로 다른 Type이 올 수 있다. 따라서, Partial<God>으로 입력하면, 아래의 코드와 같이 작동하게 된다.
type Partial<God> = {
[P in keyof God]?: God[P];
};
따라서, God 이라는 Type에 있는 key 각각을 P로 받아온 뒤, 이 P를 프로퍼티 Key로 갖고, 그 프로퍼티 Value의 타입은 God의 해당 P 프로퍼티 Value의 Type과 동일하다는 의미가 된다.
객체의 type 지정에서 프로퍼티 Key 부분에 Array가 지정되는 문법인 아래 코드와 함께 천천히 생각해보길 바란다.
type ArryKey = {
[key : string] : string
}
따라서 결과적으로 Partial<God>은 아래와 같은 코드로 작동한다.
type Partial<God> = {
name?: string;
gender?: string;
location?: string;
};
자 그럼 다시 useParams로 돌아오자.
export function useParams<Params extends { [K in keyof Params]?: string } = {}>(): Params;
위 코드는 결국, Params Type을 { [K in keyof Params]?: string } = {}로부터 상속받아온 뒤, useParams의 return 값을 Params로 정해주는 코드이다.
그렇다면 { [K in keyof Params]?: string } = {} 이 코드는 무엇을 의미할까?
먼저 짚고 넘어갈 것은 상속이란 결국 제약을 의미한다는 점이다. 좋은 것만 쏙 골라서 상속 받을 수는 없다. 현실 세계에서도 그런 상속은 존재하지 않는다. 즉, 부모의 모든 요소를 자식이 구현해야만 한다.
그런 의미에서 Params에 존재하는 key에 대해서는 optional key를 가지며, 그 value는 string인 임의의 익명Type이 곧 Params의 부모 Type이 된다. 하지만 어쨌거나 이 복잡한 관계(?)를 지닌 부모Type의 값은 { }이다. 즉, 우리가 정해주는 Type은 결국 { }를 확장하는 셈이다.
굳이 이런 코드가 필요한 이유는, Params로 string이 아닌 다른 Type이 오지 못하도록 제한하기 위함이다.
또한, 이러쿵 저러쿵해서 결국 부모Type의 값이 { } 인 것은 아래와 같이 어떠한 Type도 전달되지 않았을 때 { }를 전달해주기 위함이다.
'Learning-Log > Computer Science' 카테고리의 다른 글
[TS] Typescript의 enum, const enum, as const 에 대해 알아보자 (0) | 2022.07.18 |
---|---|
[WSL2] Vmmem의 RAM 점유율 해결 방법 (0) | 2022.07.17 |
[JS] 모듈을 받아오는 import와 모듈을 내보내는 export (0) | 2022.07.15 |
[ReactJS] styled-components와 함께 하는 즐거운 ReactJS (0) | 2022.07.12 |
[NestJS] NestJS, 한 번 빠지면 벗어날 수 없는 마성의 늪(3) : NestJS를 이해하기 위한 First Class Function, Closure 그리고 Decorator (0) | 2022.07.11 |