티스토리 뷰
아래 글에 이어 작성된 내용입니다.
TypeScript의 기본 타입에 대하여
서론
타입스크립트는 표현력이 굉장히 강한 언어입니다. 타입만으로도 알고리즘 작성이 가능한 수준이죠.
다만 모든 타입이 표현 가능한 건 아닙니다.
예로 타입스크립트에서 튜플의 length는 최대 999까지로 제한되어 있습니다.
"표현력이 좋다면서요? 왜 제한을 두죠?"
대부분의 경우 표현할 수 없어서가 아닌, 너무 많은 연산을 사용하는 것을 방지하기 위해서 입니다.
타입스크립트 버전에 따라 제약이 풀리거나 기존에 없던 새 기능이 추가되면서
기존에는 표현할 수 없었던 타입들이 나중에는 표현 가능해지기도 합니다. ⭐
타입(Type)
이제부터 값을 다루는 자바스크립트와 타입을 다루는 타입스크립트를 엄격히 구분지어야 합니다.
앞선 글에서 타입스크립트를 제대로 사용하기 위해선 타입에 대한 이해와 고민이 필요하다고 언급했었는데요.
타입스크립트에서 말하는 타입이 무엇인지 알아가 보겠습니다.
기본 타입부터 시작합니다.
식별자
const 변수식별자: 타입식별자 = 값;
const example1: number = 1;
const example2: string = '2';
타입스크립트에서 타입을 명시한 식별자들을 표기하는 방식입니다.
콜론 기호를 이용해 타입을 명시하며 그 외에는 자바스크립트와 동일합니다.
// example3 : 인수의 개수, 타입 모두 상관 없으며, 반환값도 어떤 값이든 가능하다.
const example3: (...param: any[]) => any = () => {};
함수의 경우에도 동일하며 대신 함수는 함수의 꼴을 본뜬 타입을 명시해야 합니다.
함수는 파라미터와 리턴 타입을 명시할 수도 있습니다.
그러면 식별자에 지정 가능한 타입은 뭐가 있을까요?
원시 타입(Primitive types)과 any
자바스크립트엔 원시 값으로 string, number, boolean이 있죠.
타입스크립트에도 원시 타입으로 string, number, boolean 타입이 있습니다.
- string : 다음과 같은 문자열 값을 나타냅니다."Hello, world".
- number : 숫자를 나타냅니다. int(정수)와 float(부동소수점) 구분 없이 모두 포함합니다.
- boolean : 부울형입니다. true와 false를 나타냅니다.
대부분 강 타입 성향의 언어들은 원시형 타입들을 변수에 미리 지정해 두는 것으로 프로그래밍을 시작합니다.
타입스크립트에서 타입을 지정하지 않았다면 (+ 추론이 되지 않는다면) any로 명시됩니다.
즉, 자바스크립트는 어떠한 타입도 지정되지 않았으므로 모든 타입이 any로 작성된 타입스크립트라 할 수 있죠.
래퍼 객체와 원시 타입
자바스크립트에서는 Number, Boolean, String 등 래퍼 객체가 존재합니다.
Number(3)과 같이 원시 타입 객체를 생성할 수 있죠.
이 래퍼 객체들은 타입스크립트에선 지양하는 것이 좋습니다.
우리가 자주 사용하게 될 typeof 키워드는 값 레벨에서나 타입 레벨에서나 래퍼 객체를 가리킬 일이 없기 때문입니다.
리터럴 타입
타입스크립트에서는 대부분의 언어들과 달리 원시 타입보다 더 하위의 타입을 표현할 수 있습니다.
이를 리터럴 타입이라고 하는데, 예를 들면 `'hello world'` 타입이 있을 수 있습니다.
const e:'helloWorld' = 'helloWorld'; // 'helloWorld' 외에는 대입 시 에러가 발생한다.
'hello world'는 string 입니다. 하지만 string이 곧 'hello world'일 수는 없죠.
마찬가지로 숫자 10은 number지만 number가 10인 것은 아닙니다.
리터럴 타입은 유니온 타입과 결합하면 유용하게 사용할 수 있습니다.
또한 타입스크립트가 수많은 타입을 표현할 수 있도록 도와주고 있죠.
nullable 타입
자바스크립트에는 undefined와 null이 있습니다.
타입스크립트에도 undefined와 null 타입이 존재하고 이 둘은 엄격히 구분되는 타입입니다.
만약 객체에서 프로퍼티의 타입으로 undefined와 null이 쓰였다면 이 또한 아예 명시하지 않은 것과 별개입니다.
명시하지 않은 프로퍼티가 JavaScript에서 undefined라는 값으로 평가되는 것과는 다릅니다.
* 만약 IDE에서 이것들이 구분되지 않는다면 tsconfig.json 파일을 반드시 살펴봐야 합니다.
아직 tsconfig.json 파일이 어색하시다면 이 말은 무시하세요.
// tsconfig.json
"strictNullChecks": true
never 타입 ⭐
자바스크립트에서 모든 타입은 any입니다.
타입스크립트에서도 모든 타입은 any 안에 속한다고 볼 수 있습니다.
반대로 아무것도 속하지 않는 타입이 never입니다. 공집합을 의미하는 타입이죠.
never와 extends 조건부 타입
따라서 never에 조건을 걸 경우 조건은 실행조차 되지 않습니다.
이는 Array.prototype의 메서드들이 빈 배열일 때에는 순회를 아예 시작하지 못하는 것과 유사합니다.
never는 타입 검사도 되지 않습니다.
아무것도 없기 때문에 조건을 체크할 대상조차 없다고 이해하시면 됩니다.
type Example<T> = T extends any ? true : false;
type e = Example<never>; // true도 false도 아닌 never로 추론된다.
never를 검증하는 법
그럼 어떻게 해야 never 추론해 낼 수 있을까요?
타입을 never로 지정하는 경우는 드물겠지만, 검증하는 법을 아예 모르면 안되겠죠.
바로 아래와 같은 형태입니다.
type IsNever<T> = [T] extends [never] ? true: false;
type e = IsNever<never>; // e : true
* 안타깝게도 타입스크립트에서 never를 검증하기 위한 방법은 암기할 수밖에 없습니다! <제네릭>에 대해 모르신다면 일단 그렇구나 하고 넘겨주세요.
유니온 타입(Union type)
유니온 타입은 '또는'의 의미로 해석하면 되고 파이프`|`를 이용해 표기합니다.
type Example = number | string;
const e1: Example = 1;
const e2: Example = '1';
`Example` 숫자 또는 문자열을 의미하는 타입입니다. 따라서 해당 타입을 가진다면 1과 '1' 모두 대입이 가능합니다.
타입추론(Type Inference)
아래 `e`타입은 `true | false`, true 또는 false을 가지는 타입입니다.
신기하게도 자동으로 boolean으로 추론되는 모습을 볼 수 있는데요.
type e = true | false; // e : boolean
이런 현상을 가장 적절한 타입 (Best Common Type)이라고 합니다.
해당 타입을 표현하기에 가장 적절한 표현식이 존재한다면 타입스크립트가 대치해 주는거죠.
이런 가장 적절한 타입을 알아내는 것이 바로 타입 추론입니다.
type Example1 = 1 | 2 | 3 | number; // number로 추론.
type Example2 = 'a' | 'b' | 'c' | string; // string으로 추론.
type Example3 = true | false; // boolean으로 추론.
타입스크립트는 직접 타입을 명시하지 않은 경우에도, 추론 가능한 범위 내에서 가장 적절한 타입을 알려줍니다.
이는 유니온 타입에서 매우 유용하게 작용합니다.
인터섹션 타입(Intersection type)
'또는'이 있다면 '그리고' 타입도 있어야겠죠.
'그리고'를 의미하는 타입, 인터섹션 타입 입니다.
번역하면 교집합이라 의미이고, `&`기호로 나타냅니다.
즉, 정의되어 있는 요소에 속할 수 있는 교집합을 표현한다고 보면 됩니다.
type Element1 = 1 | 2 | 3;
type Element2 = 2;
type Example = Element1 & element2;
const e1: Example = 2;
1부터 3까지의 수를 의미하는 타입 `Element1`과 오직 2만을 허용하는 타입 `Element2`의 교집합 `Example`을 정의했습니다.
따라서 `e1`의 타입은 2이며 값 역시 2를 대입한 경우가 아니면 에러를 뱉습니다.
인터페이스 타입에서의 인터섹션
interface Person {
name: string;
age: number;
}
interface Address {
address: string;
}
type PersonWithAddress = Person & Address; // { name: strting; age: number; address: string }
유니온 타입에서의 인터섹션은 두 유니온의 공통된 부분만을 뽑아냈습니다.
그런데 인터페이스에서는 인터섹션 타입을 사용하면 두 인터페이스가 합성된 타입으로 나옵니다.
얼핏 위와 상반된 결과라 헷갈릴수 있지만, 인터섹션이 둘 다 허용되는 가장 적절한 타입을 의미한다고 이해하면 쉽습니다.
서로 다른 두 유니온 타입을 둘 다 허용하기 위해서는 그 유니온 타입의 공통된 부분이어야만 합니다.
하지만 인터페이스에서 두 인터페이스를 허용하는 가장 적절한 타입은 두 인터페이스의 병합된 형태인 것이죠.
배열 타입(Array type)
배열타입에는 가변적인 length를 가지는 Array 타입과, 고정된 length의 Tuple 타입이 있습니다.
Array는 다른 타입이 여러 개가 모였을 때를 의미하며 요소의 수에 대한 제한이 없습니다.
type Example = number[];
const e: Example = [1,2,3,4,5];
후술 할 제네릭을 이용하면 아래와 같이 선언할 수 있습니다.
type Example1 = Array<number>;
type Example2 = Array<string>;
Tuple 타입에 대해서는 추후 설명하도록 하겠습니다.
여기까지 타입스크립트의 기본 타입들에 대해 작성해 보았습니다.
이어서 타입조작(제네릭/조건부/infer 등)에 대해 작성해 보겠습니다.
감사합니다.
공부한 내용을 복습/기록하기 위해 작성한 글이므로 내용에 오류가 있을 수 있습니다.
'TS | NestJS' 카테고리의 다른 글
[TS] 타입이 추론되는 String.prototype.split - 2 (0) | 2024.03.05 |
---|---|
[TS] 타입이 추론되는 String.prototype.split - 1 (0) | 2024.03.03 |
[TS] 유틸리티(Utility) 타입 (0) | 2024.03.02 |
[TS] TypeScript의 타입 조작 (제너릭, 조건부타입, infer) (0) | 2024.03.01 |
[TS] TypeScript 시작하기 (0) | 2023.11.07 |