
타입 호환이란 서로 다른 2개의 타입이 있을 때 한 타입이 다른 타입에 포함되는지를 의미합니다.
타입 호환은 구조적 서브타이핑(subtyping)에 기반합니다. 구조적 타이핑은 멤버만을 기준으로 타입들을 연관시키는 방법입니다.
예를 들어,
interface Pet {
name: string;
}
class Dog {
name: string;
}
let pet: Pet;
pet = new Dog(); // 가능
string 타입인 name 속성을 갖는 Pet 인터페이스와 Dog 클래스를 선언하였습니다.Dog 클래스는 명시적으로 Pet 인터페이스를 상속받지 않았기 때문에 에러가 발생할 것이라고 생각할 수 있지만 결과적으로 에러가 발생하지 않습니다.let x = (a: number) => 0;
let y = (b: number, s: string) => 0;
y = x;
x = y; // Error
x는 number 타입의 매개변수를 가지고 있고, y는 number와 string 타입을 가진 매개변수를 가지고 있습니다.y = xx의 매개변수는 y의 매개변수와 호환 가능한 타입을 가지고 있기 때문에 할당이 가능합니다.x = yx 매개변수는 y의 string 타입을 가지고 있지 않기 때문에 할당이 불가능해 에러가 발생합니다.그렇다면 x와 y의 매개변수 타입이 정확하게 일치하지 않는데 y = x 할당이 가능한 이유는 무엇일까요?
▶️ 바로 자바스크립트에서는 함수의 추가 매개변수를 무시하기 때문입니다.
let items = [1, 2, 3];
items.forEach((item, index, array) => console.log(item));
items.forEach(item => console.log(item)); // 가능
예를 들어, forEach 메서드는 콜백 함수에서 사용할 수 있는 배열 요소, 요소의 인덱스, 그리고 해당 배열을 매개변수로 제공합니다. 하지만 forEach에서는 사용하지 않는 매개변수는 무시하기 때문에 하나의 매개변수만 사용하여도 에러가 발생하지 않습니다.
함수의 반환 타입 호환은 매개변수의 타입 호환과 다릅니다.
let x = () => ({ name: "Dove" });
let y = () => ({ name: "Dove", location: "Suwon" });
x = y;
y = x; // Error
x = yy에는 x의 name 프로퍼티를 가지고 있기 때문에 할당이 가능합니다.y = xx에는 y의 name 프로퍼티는 가지고 있지만 location 프로퍼티가 없기 때문에 할당이 불가능합니다.type Person = {
name: string;
};
interface Developer {
name: string;
skill: string;
}
let dove: Person = {
name: 'dove'
};
let nanami: Developer = {
name: 'nanami',
skill: 'ts'
};
dove = nanami;
nanami = dove; // Error
Person은 name 속성을 가지고 있고, Developer는 name과 skill 속성을 가지고 있습니다.dove 변수에 Person 타입을 선언하고 nanami 변수에는 Devloper 타입을 선언하고 값을 할당하였습니다.dove = nanamidove 변수의 Person 타입은 nanami 변수의 Devleoper 타입의 name 속성을 가지고 있기 때문에 타입 호환이 가능합니다.nanami = dovenanami 변수의 Developer 타입은 name과 skill 속성이 모두 선언되어야 하는데 dove 변수의 Person 타입은 name 속성만 있기 때문에 타입 호환이 불가능합니다.nanami = dove의 에러를 해결하고 싶다면 어떻게 해야 할까요?
type Person = {
name: string;
};
interface Developer {
name: string;
skill?: string;
}
let dove: Person = {
name: 'dove'
};
let nanami: Developer = {
name: 'nanami',
skill: 'ts'
};
dove = nanami;
nanami = dove;
Developer 타입의 skill 속성을 옵셔널로 변경하면 타입 에러를 해결할 수 있습니다.enum Language {
C, // 0
Java, // 1
TypeScript // 2
}
let a: number = 10;
a = Language.C;
Language.C = a; // Error
a = Language.Cnumber 타입의 변수 a에 10을 할당하고 Language 이넘의 속성 C를 할당하면 에러가 발생하지 않습니다.Language.C = aLanguage 이넘의 속성 C에 a를 할당하면 에러가 발생합니다.enum Language {
C,
Java,
TypeScript
}
enum programming {
C,
Java,
TypeScript
}
let langC: Language.C;
langC = Programming.C; // Error
langC 변수에 Language 이넘의 속성 C를 타입으로 선언하고 해당 변수에 Programming 이넘의 속성 C를 할당하면 에러가 발생합니다.제네릭의 타입 호환은 구조적 타입에 기반하고 있기 때문에 제네릭으로 받은 타입이 타입 구조 내에서 사용되었는지의 여부를 확인합니다.
interface Empty<T> { }
let x: Empty<number>;
let y: Empty<string>;
x = y;
y = x;
x와 y 변수는 Empty 인터페이스의 제네릭 타입을 사용합니다.Empty 인터페이스는 제네릭으로 타입을 넘겨받아도 타입 구조에 영향을 끼치지 않으므로 서로 타입이 호환됩니다.interface NotEmpty<T> {
data: T;
}
let x: NotEmpty<number>;
let y: NotEmpty<string>;
x = y; // Error
NoEmpty 인터페이스는 제네릭으로 타입을 넘겨 받고 해당 타입을 data 속성에서 사용합니다.NoEmpty 인터페이스는 제네릭으로 타입을 넘겨 받으면 data 속성에서 사용하기 때문에 (타입 구조 안에서 사용했기 때문에) 에러가 발생합니다.참고
📖 쉽게 시작하는 타입스크립트
🔗 https://www.typescriptlang.org/ko/docs/handbook/type-compatibility.html