타입은 '할당 가능한 값들의 집합'이라고 생각하자(예시를 들어 모든 숫자의 집합은 'number'이다.)
가장 작은 타입이며 아무 값도 포함하지 않는 공집합( ∅ )이다.
never 다음으로 작은 집합으로 한 가지 값만 포함하는 타입이다.
type A = 'A';
type B = 'B';
type Twelve = 12;
두개 혹은 세 개로 묶을 수 있는 타입이다.
type AB = 'A' | 'B';
type AB12 = 'A' | 'B' | 12;
(🚫여기서 잠깐🚫 ts 오류에서 '할당 가능한' 이라는 문구를 볼 수 있는데, 이 문구는 집합의 관점에서, '~의 원소(값과 타입의 관계)' 또는 '~의 부분 집합(두 타입의 관계)'을 의미한다.)
위 오류에 대한 예시를 들면
const a: AB = 'A'; // 정상, 'A'는 집합 {'A', 'B'}의 원소이다.
const c: AB = 'C'; //~'"C"' 형식은 'AB' 형식에 할당할 수 없다.
"C"는 유닛 타입이다 범위는 단일 값 "C"로 구성되며 AB("A"와"B"로 이루어진)의 부분 집합이 아니므로 오류이다. 집합의 관점에서, 타입 체커의 주요역할은 하나의 집합이 다른 집합의 부분 집합인지 검사하는 것이라고 볼 수 있다.
집합의 관점에서 타입 체커는 하나의 집합이 다른 집합의 부분 집합인지 검사하는 역할을 가졌다.
const ab: AB = Math.random() < 0.5 ? 'A' : 'B'; // 정상
const back: AB = 1; // '1' 형식은 'AB' 형식에 할당할 수 없습니다.
type AB = 'A' | 'B';
type AB12 = 'A' | 'B' | 12
const ab: AB = Math.random() < 0.5 ? 'A' : 'B'; // 정상, {"A", "B"}는 {"A", "B"} 의 부분집합
const ab12: AB12 = ab; // 정상, {"A", "B"} 는 {"A", "B", 12} 의 부분집합
declare let twelve: AB12;
const back: AB = twelve;
// Type 'AB12' is not assignable to type 'AB'.
// Type '12' is not assignable to type 'AB'.
// AB12 타입의 값은 AB 타입의 변수에 할당 불가능하다. 부분집합이 아니다.
아래 코드에서 Person와 Lifespan가 공통적으로 가지는 속성이 없기 때문에 PersonSpan은 never 타입일 것이다.
interface Person {
name: string;
}
interface Lifespan {
birth: Date;
death?: Date;
}
type PersonSpan = Person & Lifespan;
// Person와 Lifespan가 공통적으로 가지는 속성이 없다.
// PersonSpan은 never 일까?
그러나 타입 연산자는 인터페이스 속성이 아닌, 값의 집합(타입의 범위) 에 적용된다.
따라서 구조적 타이핑에 의해 추가적인 속성을 가지는 값도 여전히 그 타입에 속하게 된다. 그래서 Person과 Lifespan을 둘 다 가지는 값은 인터섹션 타입에 속하게 된다.
const ps: PersonSpan = {
name: 'Alan Turing',
birth: new Date('1912/06/23'),
death: new Date('1954/06/07'),
} // 정상
값 ps 는 Person 타입에 속하는가? => name 속성을 가지고 있기 때문에 다른 속성들을 가지고 있어도 구조적 타이핑에 의해 Person타입에 할당 가능하다.
값 ps 는 Lifespan 타입에 속하는가? => birth 속성을 가지고 있기 때문에 다른 속성들을 가지고 있어도 구조적 타이핑에 의해 Lifespan타입에 할당 가능하다.
값 ps 는 PersonSpan 타입에 속하는가? => Person과 Lifespan을 둘 다 가진다.
💥인터섹션 타입의 값(속성에 대한 인터섹션)은 각 타입 내의 속성을 모두 포함하는 것이 일반적인 규칙이다.
인터섹션 타입의 값(속성에 대한 인터섹션)은 각 타입 내의 속성을 모두 포함하는 것이 규칙이다. 하지만 두 인터페이스의 유니온(속성에 대한 유니온)에서는 그렇지 않다.
interface Person {
name: string;
}
interface Lifespan {
birth: Date;
death?: Date;
}
type K = keyof (Person | Lifespan); // never
Person과 Lifespan의 합집합에 속하는 값은 어떠한 키도 없기 때문에, 유니온에 대한 keyof는 never이다.
Person | Lifespan 에 { name: string; } 을 할당 가능한가? => Person 에 포함되기 때문이다.
Person | Lifespan 에 { birth: Date; } 을 할당 가능한가? => Lifespan 에 포함되기 때문이다.
{ name: string; }, { birth: Date; } 의 합집합은 name 을 가질 수도, 안가질 수도 있으며 birth 또한 그러하다. 따라서 Person | Lifespan에 속하는 값은 어떤 키를 가질지 알 수 없다.
type A = {
a: string
}
type B = {
b: string;
}
// keyof (A&B) 와 동일하다.
type AnB = (keyof A) | (keyof B); // "a" | "b" -> 각 속성을 모두 포함
// keyof (A|B) 와 동일하다.
type AuB = (keyof A) & (keyof B); // never
타입의 합집합은 더 넓은 값의 범위를 가진다는 의미이다.
type C = A | B ;
C는 A도 포함하고 B도 포함한다. 즉 A도 C를 만족하며 B도 C를 만족한다.
만약 A한테만 name 프로퍼티가 있다면 C 타입을 매개변수로 받아 name 프로퍼티에 접근하려 할 때 B 에는 name이 없다는 오류가 발생할 것이다.
즉 C타입이 A타입보다 범위가 넓어졌기 때문에 발생한 오류다.
타입의 교집합은 값의 범위가 더욱 좁아진다는 의미이다.
interface A {
name: string;
}
interface B {
age: number;
}
type C = A & B;
const c: C = {
name: 'hee',
age: 25
}
타입 C는 A도 만족해야하고 B도 만족해야한다. 즉 C 의 값의 범위는 훨씬 좁아졌다.
만약 age가 없거나 name이 없다면 타입C 를 만족시키지 못한다.
type T = Exclude<string|Date, string|number>; // 적절한 경우 - 타입이 Date가 됨
type NonZeroNums = Exclude<number, 0>; // 적절하지 못한 경우 - 타입은 여전히 number
keyof는 객체의 키타입을 반환한다. 즉 객체의 키값의 집합이다.
interface P{
x: number;
y: number;
}
type PKey = keyof P; // "x" | "y"
ts의 symbol은 타입 공간이나 값 공간 중 한 곳에 존재한다. ts가 사용하는 공간과 값 공간(변수, 함수 등)은 서로 분리되어 있어서 같은 이름으로 사용할 수 있다. 즉 같은 이름의 타입과 식별자가 있으면 둘이 속한 공간이 다르게 된다.
(Symbol은 ES 6에서 추가 된 자료형이다. 자바스크립트에서 Boolean, string, number, null, undefined와 같이 객체와 메소드가 아닌 데이터이다.)
// 서로 다른 공간을 사용하기 때문에 오류가 나지 않음
type Person = {
name: string;
age: number;
};
const Person = {
name: "alice",
age: 26,
};
interface A { // 타입
b: number;
}
const A = 1; // 변수
위 예제는 오류가 발생하지 않는데 타입A와 값 A는 서로 아무런 관련이 없다.
interface Cylinder {
radius: number;
height: number;
}
const Cylinder = (radius: number, height: number) => ({ radius, height });
function calculateVolume(shape: unknown) {
if (shape instanceof Cylinder) {
// instanceof는 타입이 아닌 함수를 참조한다.
shape.radius; // '{}' 형식에 'radius' 속성이 없습니다.
}
}
이번 예제도 마찬가지로 shape instanceof Cylinder 에서 Cylinder 는 함수로 참조된다.
type T1 = 'string literal';
const v1 = 'string literal';
위에서 type 으로 선언한 T1 은 타입, const로 선언한 v1 는 값이다. 이를 js 코드로 컴파일 하면
const v1 = 'string literal';
즉 타입은 컴파일 하는 과정에서 제거된다.
interface Person {
name: string;
}
const p: Person = { name: 'hee' };
type T = typeof p; // 타입은 Person
const v1 = typeof p; // 값은 'object'
class Cylinder {
radius=1;
height=1;
}
const v = typeof Cylinder; // 값이 function
type T = typeof Cylinder; // 타입이 class Cylinder, 즉 생성자 함수
const c = new fn(); // 타입이 Cylinder
만약 클래스의 인스턴스를 타입으로 사용하고 싶다면 다음과 같이 InstanceType를 작성하면 된다.
type C = InstanceType<typeof Cylinder>; // 타입이 Cylinder
타입에서 []로 접근하는 방법을 의미한다. 문법적 오류가 아니면 어떠한 값도 넣을 수 있다.
속성 접근자 []은 값 또는 타입으로 사용할 때 동일하게 동작한다.하지만 접근자는 타입의 속성을 얻을 때 사용할 수 없다.
따라서 타입의 속성을 얻을 때에는 반드시 [] 접근자를 사용해야 한다.
//EX.1
type Person = {
name: string;
age: number;
vision: {
right: number;
left: number;
};
};
const vision1: Person["vision"] = {
right: 1.2,
left: 1.5,
};
// 아래와 같이 속성 접근자에는 "어떤한 값도 가능"합니다.
const vision2: Person[keyof Person] = {
right: 1.2,
left: 1.5,
};
//EX.2
type Person = {
first: string;
age: number;
}
const pigme = {
first: 'hee',
age: 20,
}
const first: Person['first'] = pigme['first']; // 정상, string
const first2: Person.first = pigme['first']; // 오류,
// Cannot access 'Person.first' because 'Person' is a type, but not a namespace. Did you mean to retrieve the type of the property 'first' in 'Person' with 'Person["first"]'?
this
&, |
const
extends
in