Type Syntax
리터럴 타입
특정한 숫자나 문자열 같이 변수의 값을 타입으로 하는 타입입니다. 각 리터럴 타입들은 string이나 number 같은 더 큰 타입에 포함됩니다.
const name = 'codeit'; // 'codeit' 이라는 리터럴 타입
const rank = 1; // 1 이라는 리터럴 타입
타입 별칭
복잡한 타입에 이름을 붙이고 재사용하고 싶을 때 사용합니다.
type Point = [number, number];
type SearchQuery = string | string[];
type Result = SuccessResult | FailedResult;
type Coupon =
| PromotionCoupon
| EmployeeCoupon
| WelcomCoupon
| RewardCoupon;Enum과 타입 별칭
아래 두 코드는 거의 같은 역할을 하는 코드입니다.
- Enum을 사용한 경우 (권장)
enum UserType { Admin = 'admin', User = 'user', Guest = 'guest', }
const role = UserType.Admin; console.log(role === UserType.Guest);
- **타입 별칭과 Union을 사용한 경우**
```tsx
type UserType = 'admin' | 'user' | 'guest'
const role: UserType = 'admin';
console.log(role === 'guest');
코드 양으로만 보면 타입 별칭을 쓰는 게 훨씬 간단해 보이는데요. 두 코드는 어떤 차이가 있을까요?
- JavaScript로 트랜스파일링 했을 때
우선 Enum 코드를 자바스크립트로 트랜스파일링해 보면 다음과 같습니다.
"use strict"; var UserType; (function (UserType) { UserType["Admin"] = "admin"; UserType["User"] = "user"; UserType["Guest"] = "guest"; })(UserType || (UserType = {})); const role = UserType.Admin; console.log(role === UserType.Guest);
Enum은 별도의 자바스크립트 객체를 만들어서 그 객체를 사용합니다. `UserType`은 자바스크립트에서 아래와 같은 객체인 거죠.
```tsx
{ Admin: 'admin', User: 'user', Guest: 'guest' }
예를 들어서 가능한 `UserType` 값들을 모두 활용해서 어떤 동작을 구현하고 싶다면 Enum을 써서 `Object.keys()`라는 함수를 사용해 볼 수 있겠죠.
```tsx
console.log(Object.keys(UserType)); // ['Admin', 'User', 'Guest']
반면에 타입 별칭은 타입스크립트에서만 의미 있는 코드입니다. 그래서 Enum과 달리 자바스크립트로 트랜스파일 했을 때 추가로 객체 같은 걸 만들지 않고 단순히 값만 사용하는 코드가 만들어집니다.
```tsx
"use strict";
const role = 'admin';
console.log(role === 'guest');
- 어떤 걸 써야 할까? 대부분의 경우 Enum 또는 타입 별칭을 모두 사용할 수 있습니다. 하지만 되도록 Enum의 목적에 맞는 경우라면 Enum 문법을 사용하시는 걸 권장드립니다.
Interface와 타입 별칭
아래 두 코드는 거의 같은 역할을 하는 코드입니다.
- Interface를 사용한 경우 (권장)
interface Entity { id: string; createdAt: Date; updatedAt: Date; }
interface User extends Entity { username: string; email: string; }
- **타입 별칭을 사용한 경우**
```tsx
type Entity = {
id: string;
createdAt: Date;
updatedAt: Date;
}
type User = Entity & {
username: string;
email: string;
}
Interface의 상속과 Intersection의 가장 큰 차이점은 Intersection은 두 가지 이상의 타입을 한 번에 합칠 수 있다는 건데요. 이것도 Interface로 아주 불가능하지는 않습니다.
```tsx
interface Entity { id: string; }
interface TimestampEntity extends Entity { createdAt: Date; updatedAt: Date; }
interface User extends TimestampEntity { username: string; email: string; }
```tsx
type Id = {
id: string;
}
type Entity = {
createdAt: Date;
updatedAt: Date;
}
type User = Id & Entity & {
username: string;
email: string;
}
- 어떤 걸 써야 할까? 대부분의 경우 Interface와 타입 별칭을 둘 다 사용할 수 있을 겁니다. 하지만 되도록 Interface의 목적에 맞는 경우라면 Interface를 사용하는 걸 권장드립니다.
타입 별칭은 언제 쓰면 좋을까?
타입 별칭은 타입에 '이름'을 정하는 문법입니다. 복잡한 타입을 만들고, 그 타입을 여러 곳에서 활용할 때 사용하면 됩니다. 예를 들자면 아래처럼 복잡한 타입을 만들고 여러 곳에서 재활용할 수 있겠죠.
type Point = [number, number];
type SearchQuery = string | string[];
type Result = SuccessResult | FailedResult;
type Coupon =
| PromotionCoupon
| EmployeeCoupon
| WelcomCoupon
| RewardCoupon
;
Union
A이거나 또는 B인 경우를 타입으로 만들고 싶을 때
ClothingProduct | ShoeProductIntersection
A와 B의 성질을 모두 갖는 타입을 만들고 싶을 때
interface Entity {
id: string;
createdAt: Date;
updatedAt: Date;
}
type Product = Entity & {
name: string;
price: number;
membersOnly?: boolean;
}하지만 보통 이럴 때는 interface와 상속을 사용하시는 걸 권장드립니다.
interface Entity {
id: string;
createdAt: Date;
updatedAt: Date;
}
interface Product extends Entity {
name: string;
price: number;
membersOnly?: boolean;
}
Intersection 하면 합쳐지는 이유
앞에서 Union과 Intersection을 배웠습니다. 간단하게는 A | B라고 하면 "A 타입이거나 B 타입이다", A & B라고 하면 "A 타입이랑 B 타입을 합친 것이다"라고 이해할 수 있었는데요. 그런데 자세히 생각해 보면 헷갈리는 부분이 있습니다. 혹시 수학에서 집합에 대해 배웠던 분들이라면 Union(합집합), Intersection(교집합)과 같은 용어를 들었을 때 아래와 같은 그림을 떠올리셨을 텐데요. 두 타입을 Union 하면 왠지 두 타입의 형태를 합쳐야 할 거 같고, Intersection을 하면 두 타입의 공통된 부분만 타입으로 될 거 같습니다. 그런데 Intersection을 하면 두 타입의 모양을 합친다니까 뭔가 이상합니다. 왜 그럴까요? 이번 레슨에서는 이 부분에 대해서 자세히 알아보도록 하겠습니다.
Structural Subtyping
타입스크립트에서 타입은 Structural Subtyping이라는 규칙을 따릅니다. 쉽게 말해서 구조가 같으면 같은 타입이라고 판단하는 건데요. 예를 들어서 a라는 프로퍼티를 갖는 타입 A가 있다고 해보죠. 이 타입의 a 프로퍼티를 출력하는 printA()라는 함수가 있습니다. 이 함수를 아래 코드와 같이 { a: 'codeit' }, { a: 'codeit', b: 42 }, { a: 'codeit', b: 42, c: true }라는 객체로 실행해도 모두 올바른 타입입니다. 세 객체 모두에 a라는 프로퍼티가 있기 때문에 타입 A라고 판단하는 거죠. 이런 걸 Structural Subtyping, Structural Type System이라고 부릅니다.
interface A {
a: string;
}
interface B {
b: number;
}
function printA(arg: A) {
console.log(arg.a);
}
const x = { a: 'codeit' };
const y = { b: 42 };
const z = { a: 'codeit', b: 42 };
const w = { a: 'codeit', b: 42, c: true };
printA(x);
printA(y); // 잘못된 타입
printA(z);
printA(w);
용어가 조금 어려워 보입니다. 쉽게 생각해서 "같은 모양이 있으면 같은 타입이라고 판단한다"라고 이해할 수 있습니다. 앞으로의 내용을 설명하기 위해서 간단하게 아래와 같은 그림으로 나타내 볼게요. 원 안에 있는 것들은 타입 A에 해당하는 객체들입니다.
마찬가지로 타입 B까지 그림으로 표현하면 아래와 같이 표현할 수 있을 겁니다.
Union 타입 살펴보기
앞에서 Union 타입은 "A 타입이거나 B 타입이다"라고 표현했습니다. 이걸 보다 정확하게 그림으로 이해해 보면 아래와 같습니다. 그래서 { a: 'codeit' }과 { b: 42 }는 물론이고 { a: 'codeit', b: 42 }도 타입 A | B라고 사용할 수 있습니다.
코드로 확인해 봐도 printAUnionB()라는 함수에 모두 타입 오류 없이 사용할 수 있다는 걸 알 수 있죠. 참고로 함수 안에서 if문으로 in 키워드를 사용해서 해당하는 프로퍼티가 존재하는지 확인해 봤는데요. 이런 식으로 타입의 범위를 좁힐 수도 있습니다. 타입스크립트에서는 이런 걸 Type Narrowing이라고 표현하니까 참고로 알아두시면 좋을 거 같네요.
interface A {
a: string;
}
interface B {
b: number;
}
function printAUnionB(arg: A | B) {
// 여기서는 타입 A | B
if ('a' in arg) {
// 여기 안에서는 타입 A
console.log(arg.a);
}
if ('b' in arg) {
// 여기 안에서는 타입 B
console.log(arg.b); // VS Code에서 arg에 마우스를 호버해 보세요.
}
}
const x = { a: 'codeit' };
const y = { b: 42 };
const z = { a: 'codeit', b: 42 };
const w = { a: 'codeit', b: 42, c: true };
printAUnionB(x);
printAUnionB(y);
printAUnionB(z);
printAUnionB(w);
Intersection 타입 살펴보기
마찬가지로 Interesection을 살펴봅시다. 앞에서 A & B라고 하면 "A와 B 두 타입을 합친다"라고 간단히 표현했습니다. 이걸 보다 정확하게 그림으로 이해해 보면 아래와 같습니다. { a, b } 두 프로퍼티를 모두 가지고 있는 타입들이 A & B라고 할 수 있겠죠? 그래서 객체의 모양만 본다면 마치 Intersection이 두 타입의 모양을 합치는 것처럼 생각할 수 있습니다.
interface A {
a: string;
}
interface B {
b: number;
}
function printAIntersectionB(arg: A & B) {
console.log(arg.a);
console.log(arg.b);
}
const x = { a: 'codeit' };
const y = { b: 42 };
const z = { a: 'codeit', b: 42 };
const w = { a: 'codeit', b: 42, c: true };
printAIntersectionB(x); // 타입 오류
printAIntersectionB(y); // 타입 오류
printAIntersectionB(z);
printAIntersectionB(w);
Structural Subtyping이라는 개념을 통해서, Union과 Intersection에 대해 그림으로 더 자세하게 알아보았습니다.
간단하게는 A | B라고 하면 "A 타입이거나 B 타입이다", A & B라고 하면 "A 타입이랑 B 타입을 합친 것이다"라고 생각하면 대부분의 경우 문제없이 사용하실 수 있을 텐데요.
혹시 앞으로 타입스크립트를 사용하면서 interface의 상속을 활용하거나, 여러 타입을 조합해서 복잡한 타입을 다루게 될 때 오늘 배운 이 내용을 토대로 한번 생각해 보시면 혼란을 줄이는데 도움이 되실 겁니다.
keyof 연산자
객체 타입에서 프로퍼티 이름들을 모아서 Union한 타입으로 만들고 싶을 때 사용합니다.
interface Product {
id: string;
name: string;
price: number;
membersOnly?: boolean;
}
type ProductProperty = keyof Product; // 'id' | 'name' | 'price' | 'membersOnly';
typeof 연산자
자바스크립트 코드에서 사용하면 결괏값이 문자열이지만, 타입스크립트 코드에서 쓸 때는 결과 값은 타입스크립트의 타입입니다.
const product: Product = {
id: 'c001',
name: '코드잇 블랙 후드 집업',
price: 129000,
salePrice: 98000,
membersOnly: true,
};
console.log(typeof product); // 문자열 'object'
const product2: typeof product = { // 타입스크립트의 Product 타입
id: 'g001',
name: '코드잇 텀블러',
price: 25000,
salePrice: 19000,