본문 바로가기

IT

Effective Typescript - 7 - 타입이 값들의 집합이라고 이해하기

타입스크립트의 타입은 '할당 가능한 값들의 '집합'으로 이해하는 것이 좋습니다. (타입의 범위)

 

가장 작은 타입 집합은 아무 값도 포함하지 못하는 집합인 never입니다. never로 선언된 변수에는 어떠한 값도 할당 불가입니다.

그다음으로

작은 집합은 한 가지 값만 포함하는 타입입니다. 이들은 타입스크립트에서 유닛(unit) 타입 이라고도 불리는 리터럴(literal)입니다.

type A = 'A';
type B = 'B';
type Twelve = 12;

복수개의 리터럴 타입을 묶으려면 유니온(union) 타입을 사용합니다. 유니온은 타입연산자 '|'를 이용해서 만듭니다.

집합에서 합집합의 개념으로 이해할 수 있습니다.

type A = 'A';
type B = 'B';
type Twelve = 12;

type AB = A | B;
type AB12 = A | B | Twelve;

앞서 설명한 타입들은 범위가 유한하므로 우리가 이해하기 쉽습니다.

그러나 우리가 쓰는 대부분의 타입들의 범위는 무한대로 넓은 경우가 많습니다. 따라서 상대적으로 이해하기 어려울 수 있습니다.

 

타입이 할당 가능한 값들의 집합이라는 개념에서 아래 예제를 보십시오.

interface Identified {
    id: string;
};

const test = {
    id : 'test',
    test: 3,
    4: 5
}

const test2 : Identified = test; // 정상

Identified 타입에는 'id' 속성에 값이 string인 어떠한 객체도 허용 가능합니다. 이 것을 이해한다면 타입이 할당 가능한 값의 집합이라는 것에 좀 더 친근하게 이해할 수 있습니다.

 

이번에는 '&'이라는 타입 연산자에 대해서 알아보도록 합시다. 우리는 '&' 는 일반적으로 교집합을 의미한다고 이해합니다. 

interface Person {
    name: string;
}

interface LifeSpan {
    birth: Date;
    death?: Date;
}

type PersonSpan = Person & LifeSpan;
// PersonSpan 의 타입은 아래와 같습니다.
// {
//     name : string;
//     birth : Date;
//     death?: Date;
// }

'&' 연산이 '할당 가능한 범위에 대한 교집합'으로 이해한다면 위 예제의 주석에 대한 설명을 좀 더 이해하는데 도움이 됩니다. 그래서 Person과 LifeSpan 속성을 모두 가지는 값은 PersonSpan 타입에 속하게 됩니다.

interface Person {
    name: string;
}

interface LifeSpan {
    birth: Date;
    death?: Date;
}

type K = keyof (Person | LifeSpan); // 타입이 never;

type K2 = keyof (Person & LifeSpan);
const k2: K2 = 'name'; // 정상
const k3: K2 = 'birth'; // 정상
const k4: K2 = 'death'; // 정상
 

 

아래는 인터페이스 간 집합 관계를 설명하는 예제입니다. 상속받은 인터페이스가 타입의 범위 측면에서 상위 집합이 됩니다.

interface Vector1D { x: number; }
interface Vector2D extends Vector1D { y: number; }
interface Vector3D extends Vector2D{ z: number; }

//Vector1D는 Vector2D의 부분집합, Vector2D는 Vector3D의 부분집합.

 

extends 키워드는 제네릭 타입에서 한정자로 쓰일 수 있습니다.

declare function getKey<K extends string>(val: any, key: K): void;

getKey({}, 'x'); // 정상) 'x' 값은 string의 서브 집합임.
getKey({}, Math.random() < 0.5 ? 'a' : 'b'); // 정상) 'x' | 'b' 는 string의 서브집합.
getKey({}, 12);// 오류 12는 string의 서브집합이 아님.
interface Point {
    x: number;
    y: number;
};

declare function sortByKey<K extends keyof T, T>(vals: T[], key: K): T[];

const pts: Point[] = [
    {x: 1, y: 2},
    {x: 3, y: 4}
]
sortByKey(pts, 'x'); // 정상
sortByKey(pts, 'y'); // 정상
sortByKey(pts, 'z'); // 비정상.

타입이 집합이라는 관점은 배열과 튜플의 관계에서도 명확하게 드러납니다.

const list = [1, 2] // 타입은 number[]
const tuple: [number, number] = list // 오류