type-challenges 문제 풀이로 타입스크립트 타입 시스템 깊이 파기: Easy 난이도 - 1
일반적으로 프로젝트에서 타입스크립트를 사용한다고 해도 복잡한 타입을 만들고 적용할 일은 잘 없었던 것같습니다. 저 또한 함수의 파라미터와 리턴값에서 number, string 등 기본적인 자료형만
curt-poem.tistory.com
type-challenges 문제 풀이로 타입스크립트 타입 시스템 깊이 파기 시리즈의 2번째 포스팅입니다. 이번 역시 easy난이도입니다. easy난이도는 아마 3번째 시리즈까지 포스팅하면 끝날 것 같습니다. 그럼 바로 시작하겠습니다.
Length of Tuple
요구
배열(튜플)을 받아 길이를 반환하는 제네릭 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 unknown[]> = T['length'];
사실 이 문제는 자바스크립트에서 Array라는 자료구조를 잘 알고 있다면 가장 쉬운 문제가 될 것입니다. 자바스크립트의 Array는 Object로 구현되어 있습니다. 다만 일반 객체와 다른 점은 키가 숫자이고 length
라는 속성을 가진다는 것입니다. 배열의 특정 요소에 접근할 때 Array[1]
은 해당 객체에 1
이라는 키를 가진 키(속성)에 접근하는 것과 같습니다. 따라서 타입스크립트에서도 배열의 길이를 가져올 때 length
라는 키에 접근하면 그 값은 배열의 길이가 됩니다.
그런데 위의 제넥릭의 결과가 number
타입이 아닌 실제 배열의 길이를 상수값으로 가져오는 것에 대해 의문이 생길 것입니다. 왜냐하면 자바스크립트에서 배열의 길이는 동적이며, 실제 런타임에 배열의 길이는 충분히 변경될 수 있기 때문입니다. 그래서 배열의 길이는 숫자의 상수값이 아닌 number
가 올바를 것입니다. 맞는 말입니다. Lenth 제네릭이 배열의 길이를 상수로 반환하기 위해서는 해당 배열이 튜플이어야 합니다. 요구에서 배열(튜플)이라고 언급한 부분이 튜플인 배열을 의미합니다, 만약 튜플이 아닌 배열에 Length
제네릭을 사용한다면 결과값(타입)은 number
가 나오게 됩니다.
const myArray = [1, 2, 3];
type b = Length<typeof myArray>; // type b = number
Exclude
요구
T
에서 U
에 할당할 수 있는 타입을 제외하는 내장 제네릭 Exclude<T, U>
를 이를 사용하지 않고 구현하세요.
예시
type Result = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'
풀이
type MyExclude<T, U> = T extends U ? never : T;
이전의 글에서 T extends U ? true : false
와 같이 삼항조건 연산자를 타입스크립트에 사용할 수 있다고 하였습니다. 따라서 위의 코드를 해석한다면 T
가 U
에 할당 가능한 타입이라면 never
가 되고 그렇지 않다면 T
타입을 그대로 사용하라는 것입니다.
never
역시 지난 번 포스팅에서 사용하였지만 지금 다시 살펴보니 별다른 설명을 하지 않고 넘어갔었습니다. 타입스크립트에서 never
타입은 "절대 발생하지 않는 값"을 나타냅니다. 이 타입이 사용된 경우는 코드의 흐름상 논리적으로 불가능한 상황을 나타냅니다. never
를 요약하자면 해당 타입은 절대로 발생하지 않는 상황이어서 never
타입을 반환하는 경우 타입 추론과 같이 타입과 관련된 모든 부분에서 제외하라는 의미로 정리 할 수 있습니다. never
에 대한 자세한 설명은 곧 작성할 타입스크립트 심층 분석 2편에서 볼 수 있습니다.
그럼 다시 코드로 돌아와 코드를 해석한다면 T가 U에 포함되는 경우 T는 결과 타입에서 제외하라는 뜻으로 볼 수 있습니다. 결국 T extends U
에서 false 판정을 받은 타입만 살아남게 됩니다.
Awaited
요구
Promise와 같은 타입에 감싸인 타입이 있을 때, 안에 감싸인 타입이 무엇인지 어떻게 알 수 있을까요?
Awaited를 구현해보세요.
예시
Promise<ExampleType>
이 있을 때, ExampleType
을 어떻게 얻을 수 있을까요?
풀이
type MyAwaited<T> = T extends PromiseLike<infer U> ? MyAwaited<U> : T;
이 문제는 저에겐 많이 어려웠습니다. 이 타입은 T가 PromiseLike 객체인 경우, 그 내부의 값을 계속해서 추출하여 최종적으로는 Promise가 완료된 값을 얻어야 합니다.
먼저 T extends PromiseLike<infer U>
는 T
가 PromiseLike
객체인지 확인합니다. PromiseLike
는 이름 그대로 Promise
와 유사한 객체를 의미합니다. 즉, Promise
와 같은 구조를 가지는 인터페이스입니다. 이 인터페이스는 실제 Promise
객체와 유사한 형태를 가지며, 타입스크립트의 타입 시스템에서 비동기 작업을 다룰 때 유용합니다. Promise
와 같은 구조를 가지지만, 실제 구현이 아닌 비동기 객체를 나타냅니다.
여기서는 infer U
를 사용하여 PromiseLike
객체 내부의 값을 추출합니다. 즉 ProimseLike
의 then
메서드가 실행될 때 가지는 인자의 타입을 추출합니다.
처음에 잘못 생각한 점은 then
메서드가 반환하는 타입이 MyAwaited
의 반환 타입으로 된다고 생각했던 점입니다. 다시 생각해보면 프로미스의 구조는 다음과 같습니다.
const promise = new Promise<number>((resolve, reject) => {
resolve(42); // 프로미스의 상태를 pending에서 fulfilled로 변경하여 42라는 값으로 완료 처리
});
위의 코드를 본면 resolve
의 인자로 들어오는 값이 프로미스가 완료된 후의 값, 즉 Promise
로 감싸여있는 값이 되는 것을 쉽게 이해할 수 있습니다. 그리고 resolv
e는 프로미스가 성공적으로 완료되었음을 나타내고, resolve
를 통해 onfulfilled
콜백이 호출됩니다. 그럼 resolve
가 전달받은 인자는 그대로 onfulfilled
의 인자가 되며 프로미스가 완료됩니다. 따라서 onfulfilled
의 인자로 들어오는 값의 타입을 찾아야 하는 것입니다.
If
요구
조건 C
, 참일 때 반환하는 타입 T
, 거짓일 때 반환하는 타입 F
를 받는 타입 If
를 구현하세요. C
는 true
또는 false
이고, T
와 F
는 아무 타입입니다.
풀이
type If<C extends boolean, T, F> = C extends true ? T : F;
사실 이 문제를 가장 먼저 풀었어야 했는데 문제 번호순으로 풀다보니 뒤늦게 풀게 되었습니다. 이전글에서도 설명하였듯이, A extend ? T : F
와 같은 방식으로 삼항 조건 연산자를 사용할 수 있습니다. 이는 A
가 T
에 할당할 수 있는 타입이라면 T
를 반환 아니라면 F
를 반환하라는 뜻입니다. 위의 코드를 해석하면 C
가 true
인 경우 T
, 그렇지 않다면 F
를 반환하는 것입니다.
'개발 > TypeScript' 카테고리의 다른 글
타입스크립트 심층 분석 3 - 타입스크립트의 구조적 타이핑과 타입호환성 (0) | 2024.08.21 |
---|---|
타입스크립트 심층 분석 2 - 타입 추론, 특수 타입, const와 let 키워드 차이(+참조값 및 원시값)에 따른 타입 추론 (0) | 2024.08.12 |
타입스크립트 심층 분석 1 - 타입스크립트의 역할 및 타입스크립트의 뿌리, 타입(Type) 이해하기 (0) | 2024.08.04 |
type-challenges 문제 풀이로 타입스크립트 타입 시스템 깊이 파기: Easy 난이도 - 1 (0) | 2024.07.12 |
타입 추론부터 타입 어설션까지 알아보기(부제: Array.prototype.pop의 반환값에는 항상 undefined가 포함된다?!) (0) | 2024.07.03 |