1. 타입스크립트 컴파일러
2. 단독으로 실행할 수 있는 타입스크립트 서버
-> 편집기(VSC)를 통해 언어 서비스를 제공하도록 설정
언어 서비스: 코드 자동 완성, 오류 확인, 리팩토링, 타입 추론, 정의 찾기 등
1번이 주된 목적이지만, 2번도 1번 못지않게 중요하다.
정의 찾기(Go to Definition)를 통해 타입 선언 파일(d.ts)을 찾아보면,
타입스크립트가 동작을 어떻게 모델링 하는지 알 수 있다.
❓ 번외 pg36
typeof null
은 object
이다.typeof undefined
는 undefined
🎯 요약
타입스크립트에서 언어 서비스를 잘 이용하자
A는 B를 상속
= A는 B에 할당 가능
= A는 B의 서브 타입
= A는 B의 부분 집합
타입스크립트 용어 | 집합 용어 |
---|---|
never | 공집합 (값 할당 불가) |
unknown | 전체 집합 (어떠한 값이든 할당 가능) |
리터럴 타입, 유닛 타입 | 원소가 1개인 집합 |
유니온 타입 | 원소가 두개 이상인 집합 |
값이 A에 할당 가능 | 값 ∈ A (값이 A의 원소) |
A가 B에 상속 | A ⊆ B (A가 B의 부분집합) |
A | B (A와 B의 유니온) | A ⋃ B (A와 B의 합집합) |
A & B (A와 B의 인터섹션) | A ⋂ B (A와 B의 교집합) A와 B의 속성을 모두 가짐 |
❓pg41 규칙이 속성에 대한 인터섹션에 관해서는 맞지만, 두 인터페이스의 유니온에서는 그렇지 않다.
-> 유니온이여도 공통된 속성이 있으면 통과되지만, 없으면 정해질 수 없어서 never
interface Person{
name: string;
}
interface Lifespan{
birth: Date;
death?: Date;
}
type k = keyof(Person | Lifespan); // 타입이 never
keyof (A&B) = (keyof A) | (keyof B)
keyof (A|B) = (keyof A) & (keyof B)
❓pg46
🎯 요약
타입을 값의 집합으로 생각하자
타입스크립트에서는 타입의 공간(타입)과 값의 공간(값)이 분리되어있다.
타입스크립트의 심벌(식별자)은 타입으로 쓸 수도 있고, 값으로 쓸수도 있다.
type Fruits = "apple" | "cherry"
const Fruits = "banana"
-> 타입 정보는 컴파일 과정에서 사라지기 때문에, 심벌이 사라지면 그건 타입이다.
안 사라지면 값. TS playground 에서 감 익히기
✅ 컴파일 과정에서 사라지는 것들: :
, as
, 타입 주석
, type
, interface
등
interface Person {
name: string;
age: number;
fruits: 'apple' | 'mango' | 'peach';
}
const person1: Person = {
name: 'lee',
age: 25,
fruits: 'mango',
};
// 1. 타입에서의 typeof
// 타입스크립트 타입으로 반환
type Lee = typeof person1; // type Lee = Person
// 2. 값에서의 typeof
// 런타임에 식별자의 타입을 반환
const leeType = typeof person1;
console.log(leeType); // "object"
타입과 값, 동시에 가능한 class
, enum
class
클래스의 타입은
typeof 클래스(생성자 함수를 나타냄)
,
인스턴스의 타입은클래스
// 1. 값으로 사용
class Stone {}
console.log(typeof Stone); // 클래스 자체 타입은 function
console.log(typeof new Stone()); // 클래스로 만들어낸 인스턴스 타입은 object
// 2. 타입으로 사용
// 타입에서의 Stone 클래스의 타입은? typeof Stone?
type StoneClassType = typeof Stone
// type StoneClassType = typeof Stone
// 클래스의 생성자 함수의 타입은 typeof Stone
// Stone의 인스턴스 타입은 Stone
const newStone = new Stone();
type TypeofNewStone = typeof newStone;
// type TypeofNewStone = Stone
// 타입과 값으로 쓰임
class Cylinder{
radius = 1;
height = 1;
}
function calculateVolume(shape: unknown){
if(shape instanceof Cylinder){
shape // 정상 타입은 Cylinder
shape.radius // 정상 타입은 number
}
}
🎯 요약
타입스크립트 코드를 읽을 때, 타입인지 값인지 잘 구분하자
변수: Type
변수에 타입을 붙임as Type
강제로 타입 지타입 선언을 사용하자
interface Person { name: string };
const alice: Person = {name: 'Alice'}; // 1. 타입 선언
const bob = {name: 'Bob'] as Person; // 2. 타입 단언
const button = document.querySelector("#myButton");
// const button: Element | null
// 개발자가 확신할 수 있다면 타입 단언으로 타입을 변형해 줄 수 있다.
const button = document.querySelector("#myButton") as HTMLButtonElement;
// const button: HTMLButtonElement
// querySelector 함수 시그니처
querySelector<K extends keyof HTMLElementTagNameMap>(selectors: K): HTMLElementTagNameMap[K] | null;
querySelector<K extends keyof SVGElementTagNameMap>(selectors: K): SVGElementTagNameMap[K] | null;
querySelector<K extends keyof MathMLElementTagNameMap>(selectors: K): MathMLElementTagNameMap[K] | null;
/** @deprecated */
querySelector<K extends keyof HTMLElementDeprecatedTagNameMap>(selectors: K): HTMLElementDeprecatedTagNameMap[K] | null;
querySelector<E extends Element = Element>(selectors: string): E | null;
/* E는 Element를 상속받고,
E의 타입이 명시되어 있지 않으면 기본값으로 Element를 사용
파라미터는 string, 리턴 타입은 E or null
=> 제너릭에 리턴받고 싶은 타입을 명시해줄 수 있음
*/
// 제너릭을 통한 타입 변경
const button = document.querySelector<HTMLButtonElement>('#myButton');
// const button: HTMLButtonElement | null
🎯 요약
불가피한 상황 제외하고, 타입 단언(as Type)보다 타입 선언(: Type)을 사용하자
객체 래퍼 타입: 대문자로 시작, Number, String, Boolean
등
원시 타입: 소문자로 시작, number, string, boolean
등
타입스크립트에서 변수 선언시 객체 래퍼 타입을 지양해야하는 이유
-> 타입스크립트는 자바스크립트의 동작을 모델링 한다.
-> 자바스크립트에서 선언되는 변수들은 모두 원시 타입으로 선언된다.
-> 타입스크립트에서 변수를 객체 래퍼 타입으로 선언을 하게 된다면, 자바스크립트에서 변수의 동작과 다르게 프로그래밍 될 수 있다.
function isGreeting(phrase: String){
return ['hello', 'hi'].includes(phrase);
// 오류 -> String타입은 string타입에 할당 불가능
}
🎯 요약
타입스크립트 객체 래퍼 타입을 지양하고, 기본형 타입을 쓰자
타입 검사에 영향을 미치는 구조적 타이핑과 잉여 속성 체크가 있다.
잉여 속성 체크
- 타입에 선언된 속성이 있는지, 그 외의 속성은 없는지 확인
- 객체 리터럴을 변수에 할당하거나 함수에 매개변수로 전달할 때, 잉여 속성 체크가 수행됨
- 객체 리터럴에서만 잉여 속성 체크가 동작함 ->
엄격한 객체 리터럴 체크
라고도 불림- 오타 방지
구조적 타이핑 검사와 잉여 속성 체크는 별도의 과정
{ name: "Alice", age: 25, occupation: "Engineer" }
라는 객체 리터럴을 미리 변수에 할당(구조적 타이핑)과 객체 리터럴을 직접 할당하는 상황(잉여 속성 체크)과는 해당 변수를 할당하는 상황과 동작은 같지만, 타입스크립트는 다르게 해석하고 에러를 후자(잉여 속성 체크)의 상황만 에러를 발생시킨다.
1. 구조적 타이핑 관점
// 구조적 타이핑
interface Person {
name: string;
age: number;
}
const person1 = { name: "John", age: 30 };
const kim: Person = person1; // 정상 동작
const person2 = { name: "Alice", age: 25, occupation: "Engineer" };
const lee: Person = person2; // 정상 동작
2. 잉여 속성 체크 관점
// 잉여 속성 체크
interface Person {
name: string;
age: number;
}
const person1: Person = { name: "John", age: 30 }; // 정상 동작
const person2: Person = { name: "Alice", age: 25, occupation: "Engineer" };
// 여기서 문제 발생, 잉여 속성 체크
/*
잉여 타입 체크 에러메시지
Type '{ name: string; age: number; occupation: string; }' is not assignable to type 'Person'.
Object literal may only specify known properties, and 'occupation' does not exist in type 'Person'.ts(2322)
*/
// 1. 타입 단언
interface Person {
name: string;
age: number;
}
const me = {
name: "yoon",
age: 25,
gender: "F"
} as Person
// 2. 인덱스 시그니처
type Coffee = {
flavor: string;
[options: string]: unknown;
};
const latte: Coffee = {
flavor: 'good',
milk: true,
price: 5000,
};
❓선택적 속성만(모든 속성이 Optional)가지는 약한 타입에서는 '공통 속성 체크'가 동작함
공통 속성 체크: 약한 타입은 임시 변수로 할당해도 잉여 속성 체크와 비슷하게 검사를 진행한다.
type WeakType = {
some?: string;
thing?: number;
prop?: boolean;
};
const opts = { tHing: 12 };
const o: WeakType = opts;
// Type '{ tHing: number; }' has no properties in common with type 'WeakType
🎯 요약
잉여 속성 체크와 구조적 타이핑의 차이점을 구분하고, 잉여 속성 체크의 한계점을 기억하자
☑️ 참고 자료
아이템7 잉여 속성 체크와 구조적 타이핑
아이템9 querySelector