반응형
250x250
Notice
Recent Posts
Recent Comments
Link
관리 메뉴

짧은코딩

재귀 타입 & 템플릿 리터럴 타입 본문

TS/TS(with ZeroCho)

재귀 타입 & 템플릿 리터럴 타입

5_hyun 2023. 11. 17. 21:19

재귀 타입

TS에는 자기 자신을 다시 사용하는 재귀 타입이 있다.

type Recursive = {
  name: string;
  children: Recursive[];
};

const recur1: Recursive = {
  name: 'test',
  children: [{ name: 'test2', children: [] }],
};

이렇게 recur1은 Recursive 객체 속성 타입으로 다시 Recursive를 사용한다.

이런 것을 바로 재귀 타입이라고 부른다.

 

-컨디셔널 타입

type ElementType<T> = T extends any[] ? ElementType<T[number]> : T;

이런식으로 컨디셔널 타입에서도 사용 가능하다.

 

다만, 이렇게 재귀 타입을 잘못 사용하면 자기 자신을 계속 가져오는 상황에 빠질 수 있는 점을 주의해야 한다.

재귀 타입을 사용해 타입을 거꾸로 뒤집기

[1, 2, 3]이라는 타입이 있으면 [3, 2, 1]로 바꿀 수도 있다.

type Reverse<T> = T extends [...infer L, infer R] ? [R, ...Reverse<L>] : [];

이렇게 하면 제일 뒤에 있는걸 앞으로 하나씩 보낼 수 있다.

  1. [1, 2, 3] 배열이 있으면 L은 [1, 2], R은 3이 된다.
  2. [R, ...Reverse<L>]은 [3, ...Reverse<[1, 2]>]가 된다.
  3. 여기서 Reverse<[1, 2]>는 [2, ...Reverse<[1]>]이 된다.
  4. Reverse<[1]>은 [1, ...Reverse<[]>]이다.
  5. Reverse<[]>은 []이니까 Reverse[1]은 [1]이 된다.
  6. 따라서 Reverse<[1, 2]>는 [2, 1]
  7. Reverse<[1, 2, 3]>은 [3, 2, 1]이 된다.
type Reverse<T> = T extends [...infer L, infer R] ? [R, ...Reverse<L>] : [];

type MyTuple = [1, 2, 3];
type ReversedTuple = Reverse<MyTuple>;

const originalTuple: MyTuple = [1, 2, 3];
const reversedTuple: ReversedTuple = [3, 2, 1];

console.log(originalTuple); // Output: [1, 2, 3]
console.log(reversedTuple); // Output: [3, 2, 1]

최종적으로는 이렇게 활용할 수 있다.

매개변수 순서를 거꾸로 뒤집기

type Reverse<T> = T extends [...infer L, infer R] ? [R, ...Reverse<L>] : [];
type FlipArguments<T> = T extends (...agrs: infer A) => infer R ? (...args: Reverse<A>) => R : never;
type Flipped = FlipArguments<(a: string, b: number, c: boolean) => string>;
  1. 반환값, 매개변수의 타입을 추론
  2. 매개변수에 Reverse 타입 적용
  3. A는 이미 매개변수의 튜플이라 바로 Reverse에 적용 가능

템플릿 리터럴 타입

정교한 문자열 조작을 위해서 템플릿 리터럴을 이용할 수 있다.

type Template = `template ${string}`;
let str: Template = `template `;
str = 'template hello';
str = 'template 123';
str = 'template'; // 여기서만 에러 발생

 

띄어쓰기가 없기 때문에 마지막 문자열 뒤에서만 에러가 발생한다.

템플릿 리터럴이 유용한 경우

만약에 지역과 이동수단으로 타입을 결정하는 경우를 구현하려고 한다.

지역은 서울, 수원, 부산, 이동수단은 차, 자전거, 도보가 있다. 

만약 템플릿 리터럴이 없다면 3 * 3으로 9가지를 다 직접 코딩해야 할 것이다.

type City = 'seoul' | 'suwon' | 'busan';
type Vehicle = 'car' | 'bike' | 'walk';
type ID = `${City}:${Vehicle}`;
const id = 'seoul:walk';

이렇게 템플릿 리터럴을 활용하면 쉽게 구현할 수 있다. 그리고 지역이나 이동수단이 늘어나도 City, Vehicle 타입에 추가하면 된다.

템플릿 리터널 타입 + 제네릭 + infer로 문자열 타입의 'x' 제거하기

템플릿 리터널 타입은 제네릭과 infer를 같이 사용하면 더 강력하다. 

 

다음 예시는 '    test    '을 'test'로 만드는 것이다.

type RemoveX<Str> = Str extends `x${infer Rest}`
? RemoveX<Rest>
: Str extends `${infer Rest}x` ? RemoveX<Rest> : Str;
type Removed = RemoveX<'xxtestxx'>
  1. xxtestxx가 Str extends `x${infer Rest}`를 평가하면 xxtestxx는 x로 시작하기에 true가 된다. 그리고 Rest는 xtestxx가 되고, 다시 재귀적으로 RemoveX<'xtestxx'>가 수행
  2. RemoveX<'xtestxx'>를 하면 1번에서와 같은 원리로 RemoveX<'testxx'>가 된다.
  3. RemoveX<'testxx'> 이제 좌측 x가 다 없으니까 Str extends `x${infer Rest}`는 false가 된다. 따라서 Str extends `${infer Rest}x`에서 평가되고 x로 끝나는 문자열이라 ture가 되어, Rest는 testx가 된다. 그러고 RemoveX<'testx'>가 된다.
  4. RemoveX<'testx'>를 하면 3번에서와 같은 원리로 Remove<'test'>가 된다.
  5. 따라서 최종적으로 'test'라는 타입이 남는다.

=> 위 코드는 한쪽씩 지우는 코드이다.

양쪽 공백을 지우는 타입

type RemoveEmpty<Str> = Str extends ` ${infer Rest}`
? RemoveEmpty<Rest>
: Str extends `${infer Rest} ` ? RemoveEmpty<Rest> : Str;
type Removed = RemoveEmpty<'   test   '>;

'x'를 지우는 RemoveX를 이용하여 공백을 지우는 RemoveEmpty를 만들 수 있다.

728x90
반응형
Comments