재귀 타입

재귀 타입

function fibonacci(num) {
  if (num <= 1) return 1
  return fibonacci(num - 1) + fibonacci(num - 2);
}
  • 함수가 자기 자신을 다시 호출하는 재귀 함수처럼 타입스크립트에도 재귀 타입이 있다.

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

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

const recur2: Recursive = {
  name: 'test',
  children: [
    { name: 'test2', children: [] },
    { name: 'test3', children: [] },
  ]
}
  • 자기 자신을 타입으로 다시 사용하는 타입을 재귀 타입이라 한다.

컨디셔널 타입에서도 사용 가능

// 재귀적으로 배열의 요소 내부 접근해서 요소 타입을 겟
type ElementType<T> = T extends any[] ? ElementType<T[number]> : T;

타입 인수로 사용하는 것은 불가능

  • 이 경우는 타입 인수를 쓰지 않는 방식으로 수정해야함

type T = number | string | Record<string, T>; // error

type T = number | string | { [key: string]: T };

재귀 타입도 맥시멈 콜스택과 비슷한 에러 발생 가능

  • 재귀 타입을 선언할 때가 아닌 사용할 때 에러가 발생

type InfiniteRecur<T> = { item: InfiniteRecur<T> };
type Unwrap<T> = T extends { item: infer U } ? Unwrap<U> : T;
type Result = Unwrap<InfiniteRecur<any>>; // error

// Unwrap<{ item: { item: { item: 'hi' } } } > -> hi
// InfiniteRecur 타입은 무한하여 Unwrap 타입은 유한한 시간 안에 InfiniteRecur 타입을 처리할 수 없음##복잡한 구조도 쉽게 표현 가능

복잡한 구조도 쉽게 표현 가능

  • 재귀 타입을 사용하는 대표적인 예시는 JSON

  • 이와 같이 재귀 타입을 사용하면 복잡한 구조도 쉽게 표현 가능

type JSONType = 
  | string
  | boolean
  | number
  | null
  | JSONType[]
  | { [key: string]: JSONType };
  
const a: JSONType = 'string';
const b: JSONType = [1, false, { "hi": "json" }];
const c: JSONType = {
  prop: null,
  arr: [{}],
}
  • 배열 타입을 거꾸로 뒤집는 것도 가능

type Reverse<T> = T extends [...infer L, infer R] ? [R, ...Reverse<L>] : []
  • 매개변수 순서를 바꾸는 타입 (매개변수는 반공변성)

// 내가 첫 시도한 타입인데...
type ReverseParams<T> = T extends (...infer LP, infer RP) => any 
  ? [RP, ...ReverseParms<LP>] : []
// 정답  
type FlipArguments<T> = T extends (...args: infer A) => infer R 
  ? (...args: Reverse<A>) => R 
  : never
// 반환값과 매개변수의 타입을 추론한 뒤, 매개변수의 Reverse 타입 적용,
// A는 이미 매개변수의 튜플 상태이므로 바로 Reverse 타입 적용 가능  
type Flipped = FlipArguments<(a: string, b: number, c: boolean) => string>;

Last updated