목차
2. 타입 호환의 기준
3. 명목적 타이핑 vs 구조적 타이핑
4. 덕타이핑 vs 구조적 타이핑
5. 타입추론과 타입호환
6. Enum 타입 호환 주의 사항
7. Class 타입 호환 주의 사항
8. Generics 타입 호환
타입 호환에 대해서 알아보자!
1. "타입 호환"이란?
* 타입 호환: 타입스크립트 코드에서 특정 타입의 값을 다른 타입으로 사용할 수 있는지 여부를 결정하는 기준
2. 타입 호환의 기준
TypeScript에서 타입을 비교할 때, 객체의 구조나 모양이 일치하면 타입이 호환된다고 본다.
즉, TypeScript에서는 명목적 타이핑이 아니라 구조적 타이핑을 사용하기 때문에, 타입의 이름이 달라도 속성이 같으면 서로 호환된다.
3. 명목적 타이핑 vs 구조적 타이핑
1) 명목적 타이핑 (Nominal Typing)
- 명목적 타이핑은 객체의 구조가 아닌 이름에 기반하여 타입을 비교하는 방식이다.
- 즉, 타입의 이름이 같아야만 두 타입을 동일하게 간주할 수 있다.
- 객체가 동일한 속성을 가지고 있어도 타입의 이름이 다르면 서로 다른 타입으로 취급된다.
- 명목적 타이핑은 Java나 C# 같은 언어에서 많이 사용된다.
// Java처럼 명목적 타이핑을 사용하는 경우:
class A {
int value;
}
class B {
int value;
}
A a = new B(); // 컴파일 오류: A와 B는 이름이 다르기 때문에 서로 호환되지 않음
위 예시에서는 A와 B 클래스가 같은 value: int라는 속성을 가지고 있더라도, 타입의 이름이 다르기 때문에 서로 호환되지 않는다고 간주한다.
2) 구조적 타이핑(Structural Typing)
- 구조적 타이핑에서는 객체가 같은 구조(모양, 속성)를 가지고 있으면, 타입의 이름이 다르더라도 속성이 일치하기 때문에 같은 타입으로 간주될 수 있다.
- 타입스크립트는 구조적 타이핑을 채택하고 있기 때문에, 객체의 구조가 중요하지 타입의 이름이 중요하지 않다.
interface Ironman {
name: string;
}
class Avengers {
name: string;
}
let i: Ironman;
i = new Avengers(); // OK, 구조가 같기 때문에 호환 가능
Ironman 인터페이스와 Avengers 클래스는 둘 다 name: string이라는 동일한 속성을 가지고 있기 때문에, 타입스크립트는 둘이 구조적으로 동일하다고 판단하고, 서로 호환이 가능하다. 여기서 중요한 것은 타입의 이름이 아니라 구조라는 것이다.
4. 덕타이핑 vs 구조적 타이핑
- 덕 타이핑(Duck Typing)은 구조적 타이핑(Structural Typing)의 일종이라고 볼 수 있다. (구조적 타이핑은 덕 타이핑보다 더 넓은 개념)
- 덕 타이핑은 "오리처럼 걷고, 오리처럼 소리 내면, 그것은 오리다"라는 표현에서 유래했다.
- 즉, 객체가 특정 메서드나 속성을 가지고 있으면, 그 객체는 특정 타입으로 간주할 수 있다는 개념이다. 이는 객체의 구조에 따라 타입을 결정하는 방식이다.
- 정리하자면, 구조적 타이핑은 객체의 전체 구조를 보고 타입을 판단하는 체계적이고 공식적인 개념이며, 덕 타이핑은 그 중에서도 특정 메서드나 속성만 맞으면 타입으로 인정되는 비공식적이고 관용적인 표현이다.
구조적 타이핑의 예시
interface Bird {
fly(): void;
}
class Airplane {
fly() {
console.log("The airplane is flying");
}
}
let vehicle: Bird = new Airplane(); // OK, 구조적으로 fly() 메서드가 존재함
이 예시에서는 Airplane 클래스가 Bird 인터페이스와 구조적으로 호환되기 때문에, 구조적 타이핑에 의해 타입이 맞다고 간주된다.
덕 타이핑의 예시
class Duck {
quack() {
console.log("Quack!");
}
}
class Person {
quack() {
console.log("Quack like a duck!");
}
}
function makeItQuack(duck: { quack: () => void }) {
duck.quack();
}
makeItQuack(new Duck()); // "Quack!"
makeItQuack(new Person()); // "Quack like a duck!"
여기서 Duck과 Person 클래스는 둘 다 quack() 메서드를 가지고 있으므로, 덕 타이핑에 의해서 Person도 "오리처럼 행동"할 수 있다. makeItQuack 함수는 객체가 실제로 "오리"인지가 중요한 것이 아니라, quack() 메서드를 가지고 있느냐만 판단하므로, Person도 허용되는 것이다.
5. 타입추론과 타입호환
interface Avengers {
name: string;
}
let hero: Avengers;
// 타입스크립트가 추론한 capt의 타입은 { name: string; location: string; } 이다.
let capt = { name: "Captain", location: "Pangyo" };
hero = capt;
1. interface Avengers { name: string; }
: Avengers라는 인터페이스가 선언되었다. 이 인터페이스는 name 속성을 반드시 가지고 있어야 하며, 그 타입은 string이다.
2. let hero: Avengers;
: hero는 Avengers 타입으로 선언되었다. 즉, hero는 반드시 name: string 속성을 가져야 한다.
3. let capt = { name: "Captain", location: "Pangyo" };
: capt는 name 속성과 location 속성을 가진 객체이다. name은 문자열 "Captain", location은 문자열 "Pangyo"이다.
4. hero = capt;
: 여기서 capt 객체가 hero 변수에 할당된다. hero는 Avengers 타입이므로 name 속성만 필요하지만, capt는 location 속성도 가지고 있다.
타입스크립트의 구조적 타입 시스템에서는 객체가 더 많은 속성을 가지고 있어도, 필요한 속성(name)만 맞으면 타입 호환이 가능하다. 그래서 capt 객체를 hero에 할당할 수 있다.
6. Enum 타입 호환 주의 사항
이넘 타입은 number 타입과 호환되지만 이넘 타입끼리는 호환되지 않는다.
enum Status { Ready, Waiting };
enum Color { Red, Blue, Green };
let status = Status.Ready;
status = Color.Green; // Error
7. Class 타입 호환 주의 사항
클래스 타입은 클래스 타입끼리 비교할 때 스태틱 멤버(static member)와 생성자(constructor)를 제외하고 속성만 비교한다.
class Hulk {
handSize: number;
constructor(name: string, numHand: number) { }
}
class Captain {
handSize: number;
constructor(numHand: number) { }
}
let a: Hulk;
let s: Captain;
a = s; // OK
s = a; // OK
8. Generics 타입 호환
제네릭은 제네릭 타입 간의 호환 여부를 판단할 때 타입 인자 <T>가 속성에 할당 되었는지를 기준으로 한다.
interface Empty<T> {
}
let x: Empty<number>;
let y: Empty<string>;
x = y; // OK, because y matches structure of x
위 인터페이스는 일단 속성(member 변수)이 없기 때문에 x와 y는 같은 타입으로 간주된다.
그런데 만약 아래와 같이 인터페이스에 속성이 있어서 제네릭의 타입 인자가 속성에 할당된다면 얘기는 달라진다.
interface NotEmpty<T> {
data: T;
}
let x: NotEmpty<number>;
let y: NotEmpty<string>;
x = y; // Error, because x and y are not compatible
인터페이스 NotEmpty에 넘긴 제네릭 타입<T>이 data 속성에 할당되었으므로 x와 y는 서로 다른 타입으로 간주된다.
'Languages > TypeScript' 카테고리의 다른 글
[TypeScript] TypeScript 컴파일러(tsc)의 컴파일 과정 (2) | 2024.10.02 |
---|---|
[TypeScript] 타입 추론(Type Inference) (2) | 2024.10.01 |
[TypeScript] 타입 가드 (Type Guard) (0) | 2024.09.30 |
[TypeScript] 타입 단언 (Type Assertion) (0) | 2024.09.30 |
[TypeScript] 타입 건전성 (Soundness ) (0) | 2024.09.29 |