배열(튜플) T를 받아 첫 원소의 타입을 반환하는 제네릭 First<T>를 구현하세요.
type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]
type head1 = First<arr1> // expected to be 'a'
type head2 = First<arr2> // expected to be 3
정답
type First<T extends any[]> = T extends [infer F, ...any[]] ? F : never;
infer F
T extends [infer F, …any[]] ? F : never
infer F 는 해당 원소 타입의 첫 부분을 이런 타입으로 받아오겠다는 뜻. T가 [infer F, …any[]] 로 이루어져 있는지 확인함.
이때 …any[]는 …rest라고 생각하면 된다.
extends
개인적으로 자주 헷갈리는 문법.
제네릭에서 사용
해당 타입이 최소한 어떤 조건을 만족해야하는지 제한을 거는 것.
조건부 타입
위에 사용한 extends가 해당 부분이다. 삼항 연산자를 생각하면 된다.
상속
interface A extends B 일때 사용. A가 B를 부모로 가진다고 생각하면 됨.
배열(튜플)을 받아 길이를 반환하는 제네릭 Length<T>를 구현하세요.
type tesla = ['tesla', 'model 3', 'model X', 'model Y']
type spaceX = ['FALCON 9', 'FALCON HEAVY', 'DRAGON', 'STARSHIP', 'HUMAN SPACEFLIGHT']
type teslaLength = Length<tesla> // expected 4
type spaceXLength = Length<spaceX> // expected 5
정답
type Length<T extends readonly any[]> = T['length'];
T[’length’]
튜플은 타입스크립트의 타입 시스템에서 객체처럼 취급 받는다고 한다. 그리고 그 안에 length라는 속성이 실제로 정의 되어 있다.
아래와 같은 느낌으로 이루어져 있다고 함.
type MyTuple = ['A', 'B', 'C'];
// 💡 타입스크립트가 바라보는 MyTuple의 내부 모습 (개념적)
interface MyTupleConcept {
0: 'A'; // 인덱스 0의 타입
1: 'B'; // 인덱스 1의 타입
2: 'C'; // 인덱스 2의 타입
length: 3; // ✨ 핵심: length 속성이 구체적인 숫자 리터럴 타입 '3'으로 고정됨
// ... 그 외 push, pop, map 같은 배열 메서드들도 포함됨
}
튜플에 대해서 조금 더 설명하자면 튜플에서 타입의 마지막에 ‘?’ 를 사용하여 optional 값을 줄 수 있다.
type Either2dOr3d = [number, number, number?];
function setCoordinate(coord: Either2dOr3d) {
const [x, y, z] = coord;
// const z: number | undefined
console.log(`Provided coordinates had ${coord.length} dimensions`);
// (property) length: 2 | 3
}
T에서 U에 할당할 수 있는 타입을 제외하는 내장 제네릭 Exclude<T, U>를 이를 사용하지 않고 구현하세요.
type Result = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'
정답
type MyExclude<T, U> = T extends U ? never : T;
분산(Distribution): ****제네릭 T가 유니언 타입(예: 'a' | 'b')으로 들어오면, TypeScript는 각 구성 요소에 대해 개별적으로 조건을 검사
조건 검사
T의 개별 요소가 U에 할당 가능하다면(T extends U), never를 반환.
할당 불가능하다면, T 그대로를 반환
never의 소멸
유니언 타입 결과에서 never는 무시되어 사라진다. (예: string | never는 string이 됨)