🤔

TypeScript 타입 정의 읽기 — 몇 가지만 알면 쉬워요

Date
2021/09/05
Tags
TypeScript
Tutorial
Created by
아마 조금 더 쉬워질걸요?
Table of Contents

TypeScript에서 타입을 나타내는 문법은 JS(TS)의 문법과 다르다

많이 혼동하는 부분이다. 보통 type T = ...로 시작하는, 타입을 정의하는 문법은 JS(TS)에서 값을 정의(대입)하는 문법과는 엄연히 다르다. 그냥 독자적인 문법으로 생각하는 것이 낫다.
다른 타입 언어 시스템에서도 TypeScript의 타입 문법을 사용하는지, 해당 문법이 표준으로 등록되어 있는지 등은 모르겠지만, 위에서 설명한 둘을 엄연히 다른 문법으로 인식하는 것이 속 편하다.

Conditional Types의 조건문에 올 수 있는 문법은 제한적이다

TypeScript 4.3 기준입니다.
Conditional Types란 타입을 나타내는 문법에서 삼항 연산자를 이용해 타입을 지정하는 방식이다. 다음과 같은 타입에서 종종 볼 수 있다.
type Extract<T, U> = T extends U ? T : never
TypeScript
복사
TypeScript에 내장된 Utility Types의 Extract 타입이다. T extends U가 참이면 T를, 그렇지 않으면 never를 반환한다.
한 가지 알아두면 좋은 점은, 삼항 연산자의 조건문에 올 수 있는 문법은 (TypeScript 4.3 기준으로) T extends U 형식밖에 없다는 것이다. 정확히는, 아직까지 타입에서 조건을 나타낼 수 있는 문법이 extends 키워드밖에 없다.

extends explained

그럼 이쯤에서 "extends가 정확히 뭐야?"라고 질문할 수 있다. extends 키워드를 이해하는 방법은 두 가지가 있다.
T extends UTUT\le U와 비슷하게 생각할 수 있다.
더 정확한 표현으로, 타입을 집합으로 생각한다면, T extends UTUT\subseteq U와 비슷하게 생각할 수 있다.
너무나도 간단한 설명이다. 퀴즈를 풀어보면서 내가 제대로 이해한 게 맞는지 확인해보자. 다음 조건문들은 true로 평가될까, false로 평가될까?
64 extends number
true64number의 부분집합인가? 당연히 그렇다.
number extends 64
falsenumber는 TypeScript에 존재하는 모든 숫자를 나타내는 타입(집합)이고, 64는 숫자 64일 뿐이다. 당연히 number가 더 큰 개념이다. 따라서 이 조건절은 false를 반환한다.
string[] extends any
trueany는 TypeScript에 존재할 수 있는 모든 타입의 교집합이다. 전체집합이라고 생각할 수 있다. 이에 string의 배열은 당연히 포함된다.
string[] extends any[]
truestring의 배열보다 "아무 타입"의 배열이 더 큰 개념이다.
never extends any
truenever가 등장해서 조금 헷갈렸을 수도 있다. 하지만 never를 공집합으로 생각한다면 true로 평가되는 것이 당연하다.
any extends any
true — 같은 집합끼리의 extendstrue이다.

infer explained

타입 정의에 숱하게 등장하는 또 다른 키워드가 바로 infer이다. infer R과 같이 쓰인다.
infer 키워드도 간단히 설명하면, "placeholder"이다. infer는 조건문에 쓰이는 타입 중 하나를 이름 붙여서 빼 와서, 삼항 연산자의 true절이나 false절에 사용하기 위해 사용한다.
빈 페트병으로 달걀 노른자를 빼는 팁을 본 적이 있는가? 마치 달걀 노른자를 쏙 빼서 옆의 그릇에 옮겨놓듯이, 조건문의 타입들 중 하나를 쏙 빼서 true절이나 false절에 사용하는 것이다.

이제 읽어보자

위에서 설명했던 갖가지 비유를 가지고 다음 타입을 제대로 해석해보자.
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
TypeScript
복사
TypeScript Utility Types의 ReturnType 타입이다. 함수 타입을 인자로 받아서 해당 함수의 반환값의 타입을 반환한다.
ReturnType은 하나의 타입을 인자 T로 받아서, 어떠한 조건문을 거쳐서 R이나 any를 내놓는다. 타입을 인자로 받는 부분을 먼저 살펴보자.
T extends (...args: any) => any
TypeScript
복사
인자로 받을 타입 TT extends (...args: any) => any 조건을 만족해야 한다. 즉, 위에서 배운 대로 extends 키워드에 대한 비유를 적용해보자면, T(...args: any) => any (아무 함수)보다 작거나 같은 개념이여야 한다. 다른 말로, T는 함수여야 한다.
Right-hand side를 살펴보자.
T extends (...args: any) => infer R ? R : any
TypeScript
복사
T extends (...args: any) => infer R가 참이면 R을, 그렇지 않으면 any를 반환한다.
조건문과 조건문에 들어간 infer R을 우리가 아는 말로 다시 해석하면, "T는 함수여야 하는데, 그의 반환 타입을 R이라고 이름 붙일거고, 이를 뒤에서 사용할 것이다" 정도이다.
그런데 왜 인자로 T를 받는 부분에서 T가 함수인 것을 체크했는데 다시 extends 키워드가 등장한 것일까? 이는 infer R로 반환 타입을 이름 붙여서 가져오기 위해 함수의 타입 정의를 다시 기술한 것에 불과하다.
그리고 마지막 true절을 통해 R이라고 이름 붙인 반환 타입을 최종적으로 반환한다.
별 거 없다.