개발새발

flat 분석하기 본문

Typescript

flat 분석하기

비숑주인 2025. 5. 10. 20:22

flat은 배열의 중첩된 구조를 평평하게 만들어주는 유용한 메서드다. JavaScript에서는 단순하게 동작하지만, TypeScript에서는 그 동작을 타입 수준에서 표현하기 위해 복잡한 타입 시스템을 활용한다. 이 글에서는 flat 메서드가 어떻게 타입 추론을 수행하는지를 하나씩 분석해본다.

 

기본 사용법

const A = [[1, 2, 3], [4, [5]], 6];
const R = A.flat();       // [1, 2, 3, 4, [5], 6]
const RR = R.flat();      // [1, 2, 3, 4, 5, 6]
const R2 = A.flat(2);     // [1, 2, 3, 4, 5, 6]

 

배열 A는 3차원 구조를 갖고 있다. flat()을 호출할 때마다 한 차원씩 감소한다. 기본값은 1이며, 인수를 주면 그 깊이만큼 중첩을 해제한다.

타입스크립트의 타입 선언 위치

flat은 ES2019부터 추가되었기 때문에 lib.es2019.array.d.ts에 정의되어 있다. 기본적인 타입 선언은 아래와 같다.

interface Array<T> {
  flat<A, D extends number = 1>(this: A, depth?: D): FlatArray<A, D>[];
}

 

이 정의에서 핵심은 FlatArray<A, D>라는 타입이다. A는 원본 배열, D는 평탄화할 깊이(Depth)이다. 반환 타입은 항상 배열이므로 최종적으로 FlatArray<A, D>[]가 된다.

FlatArray 타입 구조

타입 정의의 핵심은 FlatArray 타입이다. 다음은 간략화한 구조다:

type FlatArray<Arr, Depth extends number> =
  Depth extends -1
    ? Arr
    : Arr extends ReadonlyArray<infer Inner>
      ? FlatArray<Inner, Prev[Depth]>
      : Arr;
 

Prev 튜플

타입스크립트는 숫자 타입에 대해 연산을 지원하지 않는다. 대신 아래와 같이 튜플을 사용해 Depth - 1 효과를 만든다.

type Prev = [-1, 0, 1, 2, 3, ..., 20]; // Prev[1] = 0, Prev[2] = 1, ...

 

작동 원리

FlatArray는 재귀적으로 배열을 펼쳐나간다.

  1. Depth === -1이면 재귀 종료.
  2. 그렇지 않으면 infer를 통해 배열 내부 요소 타입을 추론.
  3. 추론된 타입에 대해 Depth를 1 낮추어 다시 FlatArray 적용.

이 과정을 통해 중첩 배열을 차례대로 평탄화할 수 있다.

 

예제 분석: const R = A.flat();

type A = (number | (number | number[])[])[];
type R = FlatArray<A, 1>[];

 

재귀 분석

  • Depth: 1 → Prev[1] = 0
  • 첫 번째 재귀: A는 배열이므로 infer Inner = number | (number | number[])[]
  • 두 번째 재귀: Inner는 배열이 아니므로 종료
  • 결과: FlatArray<number | number[], 0>[] = (number | number[])[]

즉, R의 타입은 (number | number[])[]이다.

예제 분석: const R2 = A.flat(2);

type R2 = FlatArray<A, 2>[];

 

  • 첫 번째 재귀: Depth = 2 → Prev[2] = 1
  • 두 번째 재귀: Depth = 1 → Prev[1] = 0
  • 세 번째 재귀: Depth = 0 → Prev[0] = -1
  • 마지막 재귀: 종료

결과는 number[]가 된다.

유니언 타입에서의 분배 법칙 주의

유니언 타입이 extends 절에 들어가면 분배가 발생한다.

type Test = number | number[];
type Result = Test extends ReadonlyArray<infer U> ? U : never;

 

위 타입은 number extends ReadonlyArray<...> → false, number[] extends ... → true
결국 Result = never | number = number

이러한 분배 덕분에 FlatArray는 유니언 내부의 배열도 정확히 처리할 수 있다.

최대 Depth 제한

type FlatArray<Arr, Depth extends number> = ...

 

이 구조는 최대 20까지의 깊이만 지원한다. 그 이상은 Prev[Depth]가 undefined가 되어 추론이 멈춘다. 하지만 실제 코드에서 flat(22) 같은 사용은 드물기 때문에, 현실적인 제한이다.

정리

  • flat은 Depth만큼 배열의 중첩을 제거하는 메서드이며, 기본값은 1이다.
  • TypeScript는 FlatArray<Arr, Depth>를 재귀적으로 정의해 타입 추론을 수행한다.
  • Prev 튜플을 사용해 Depth를 감소시키는 트릭을 쓴다.
  • 유니언 타입에 대한 분배, infer를 활용한 내부 타입 추론 등 고급 타입 시스템 기능이 총동원된다.
  • 완벽한 타입은 아니더라도, 대부분의 사용 사례에서 정확한 타입을 추론할 수 있게 설계되어 있다.

'Typescript' 카테고리의 다른 글

bind 분석하기  (0) 2025.05.10
Promise, Awaited 타입 분석하기  (0) 2025.05.10
reduce 만들기  (0) 2025.04.30
filter 만들기  (0) 2025.04.30
map 만들기  (0) 2025.04.30