타입 호환이란 서로 다른 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 = x
x
의 매개변수는 y
의 매개변수와 호환 가능한 타입을 가지고 있기 때문에 할당이 가능합니다.x = y
x
매개변수는 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 = y
y
에는 x
의 name
프로퍼티를 가지고 있기 때문에 할당이 가능합니다.y = x
x
에는 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 = nanami
dove
변수의 Person
타입은 nanami
변수의 Devleoper
타입의 name
속성을 가지고 있기 때문에 타입 호환이 가능합니다.nanami = dove
nanami
변수의 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.C
number
타입의 변수 a
에 10을 할당하고 Language
이넘의 속성 C
를 할당하면 에러가 발생하지 않습니다.Language.C = a
Language
이넘의 속성 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