타입 시스템이란?
장점
"만약 어떤 새가 오리처럼 걷고, 헤엄치고, 꽥꽥거리는 소리를 낸다면 나는 그 새를 오리라고 부를 것이다."
동적
타이핑에서 사용된다.class Duck {
chirping() {
console.log('quack quack')
}
}
class Pigeon {
chirping() {
console.log('coo coo')
}
}
// Pigeon으로 생성했어도 에러 없이 실행된다.
// chirping이 없더라도 클래스 객체가 들어올 수는 있다. (런타임 상에서만 에러 발생)
function inTheForest(duck) {
duck.chirping()
}
inTheForest(new Duck())
inTheForest(new Pigeon())
장점
정적
타이핑에서 사용된다.장점
단점
👩🏫 TS는 JS를 모델링한 언어이므로, TS의 타입 시스템은 구조적 서브타이핑을 기반으로 합니다!
type BasicFood = {
protein: number;
carbohydrates: number;
fat: number;
}
const calcCalorie = (food: BasicFood) =>
food.protein * 4 + food.carbohydrates * 4 + food.fat * 9
type Pizza = BasicFood & {
brand: string;
}
calcCalorie
함수에 여러 타입의 객체가 전달될 경우Pizza
처럼 기존 타입을 상속하여 계산에 필요한 모든 프로퍼티를 포함하고 있을 경우 런타임 상 정상적으로 동작할 수 있는 코드가 된다.➡️ 개발자의 의도에 맞는 유연한 대응을 위해 타입 시스템은 부분적으로 타입 호환을 지원한다.
명목적 서브타이핑일 경우
// 상속 관계가 명시되어 있다.
type Pizza = BasicFood & {
brand: string;
}
const pizza: Pizza = {
protein: 30,
carbohydrates: 50,
fat: 15,
brand: "Domino"
}
const calorie = calcCalorie(pizza) // 오류 없이 실행된다.
구조적 서브타이핑일 경우
// 상속 관계를 명시하지 않았다.
// pizza는 BasicFood 타입의 프로퍼티를 모두 포함하고 있다.
const pizza = {
protein: 30,
carbohydrates: 50,
fat: 15,
brand: "Domino"
}
const calorie = calcCalorie(pizza) // 오류 없이 실행된다.
Freshness
const calorie = calculateCalorie({
protein: 30,
carbohydrates: 50,
fat: 15,
brand: 'Domino'
})
parser.ts
, scanner.ts
)binder.ts
, checker.ts
등에서 타입 검사를 수행하고 JS 소스코드로 변환한다.emitter.ts
, transformer.ts
등에서 AST를 JS 소스코드로 변환한다.➡️ checker.ts
의 hasExcessProperties()
함수에서 구조적 서브타이핑과 타입 호환에 관련된 부분을 처리한다.
// 함수의 매개변수로 전달된 값이 FreshLiteral이라면 true
const isPerformingExcessPropertyChecks =
getObjectFlags(source) & ObjectFlags.FreshLiteral;
// FreshLiteral일 경우
if (isPerformingExcessPropertyChecks) {
// hasExcessProperties() 함수는 excess property가 있는 경우 에러 반환
// property가 정확히 일치하는 경우만 허용하는 것으로 타입 호환을 허용하지 않는 것과 같은 의미
if (hasExcessProperties(source as FreshObjectLiteralType)) {
reportError();
}
}
// FreshLiteral이 아닌 경우 위 분기를 건너뛰고 타입 호환을 허용하게 된다.
FreshLiteral
여부에 따라 조건 분기가 발생하고, 타입 호환 허용 여부가 결정된다.FreshLiteral
이란 왜 존재할까?구조적 서브타이핑에 기반한 타입 호환의 단점
const calorie = calculateCalorie({
protein: 30,
carbohydrates: 50,
fat: 15,
brand: 'Domino'
})
brand
프로퍼티를 사용하는 것으로 오해할 수 있다.brand
를 br
등으로 쓰는 것처럼 오타가 발생하더라도 excess property이르모 오류가 발견되지 않는다.Freshness
그럼에도 fresh object에 타입 호환을 허용하고 싶다면?
type Food = {
protein: number;
carbohydrates: number;
fat: number;
// index signature
[x: string]: any
}
tsconfig.json
에 suppressExcessPropertyErrors
를 true
로 설정한다.모든 경우에 타입 호환을 막아버리고 싶다면?
__brand
와 같은 프로퍼티를 추가한다.type Brand<K, T> = K & { __brand: T};
type Food = Brand<{
protein: number;
carbohydrates: number;
fat: number;
}, 'Food'>
const pizza = {
protein: 100,
carbohydrates: 100,
fat: 100,
brand: 'Domino'
}
calculateCalorie(burger) // 오류 발생 (Property '__brand' is missing)