TypeScript에서 인터페이스(interface)와 타입(type)은 모두 객체의 형태와 구조를 정의하는 데 사용됨.
인터페이스는 TypeScript가 처음 설계될 때부터 있던 기능임. Java와 같은 객체 지향 언어의 영향을 받아 클래스와 연동되는 구조적인 정의가 필요했기 때문에 인터페이스가 도입되었음.
타입은 이후 TypeScript에 추가된 기능으로, 더 유연하고 함수형 프로그래밍 스타일을 지원하는 방식으로 설계되었음. 다양한 데이터 구조를 유연하게 정의할 수 있는 도구로 발전했음.
인터페이스는 선언적 확장이 가능
즉, 동일한 이름의 인터페이스를 여러 번 선언할 수 있으며, TypeScript는 이를 하나의 인터페이스로 병합함. 이는 코드 확장성과 유지보수 측면에서 큰 장점을 가짐.
하지만 이미 선언된 interface를 모르고 중복 사용할 경우 오류가 나는 경우가 있어 이는 단점이 될 수도 있음.
interface User {
name: string;
}
interface User {
age: number;
}
const user: User = {
name: "박지성",
age: 25, // 병합된 인터페이스를 통해 이름과 나이 모두 사용 가능
};
타입은 확장될 수 없고, 한 번 선언된 타입은 재선언할 수 없음.
type User = {
name: string;
};
// Error: Duplicate identifier 'User'. 발생
type User = {
age: number;
};
타입은 유니온과 인터섹션 타입을 지원함.
즉, 여러 타입을 조합하거나(유니온), 합칠 수(인터섹션) 있음.
type Pet = { name: string };
type Dog = Pet & { breed: string }; // 인터섹션
const dog: Dog = { name: "Buddy", breed: "Labrador" };
유니온 타입도 가능
type StringOrNumber = string | number;
반면, 인터페이스는 유니온 타입을 지원하지 않음.
인터페이스는 주로 클래스와 연동해서 사용됨.
TypeScript에서 인터페이스는 클래스의 형태를 정의하고 클래스가 특정한 구조를 따르게 강제할 수 있음.
interface Animal {
name: string;
speak(): void;
}
class Dog implements Animal {
name = "Buddy";
speak() {
console.log("Woof!");
}
}
타입은 클래스에 직접적으로 사용되기보다는 객체의 구조를 정의하거나 함수의 반환 타입 등을 정의하는 데 자주 사용됨.
- 확장성: 여러 번 선언이 가능하고 병합되므로 코드 확장이 용이함.
- 클래스와의 자연스러운 연동: 클래스가 특정 구조를 따르게 할 때 유용함.
- 유연성 부족: 유니온 타입이나 인터섹션 타입을 지원하지 않음.
- 구조적 제한: 더 복잡한 구조나 동적 타입을 선언하는 데 제약이 있음.
- 유연성: 유니온과 인터섹션 타입 지원으로 복잡한 타입을 표현할 수 있음.
- 복합 타입 지원: 객체, 유니온, 인터섹션 등 다양한 형태의 타입 선언이 가능.
- 확장성 부족: 한 번 선언된 타입은 재선언할 수 없으므로 확장성 면에서 인터페이스보다 제한적임.
interface User {
name: string;
age: number;
}
const user: User = {
name: "박지성",
age: 25,
};
type User = {
name: string;
age: number;
};
const user: User = {
name: "박지성",
age: 25,
};
type StringOrNumber = string | number;
let value: StringOrNumber;
value = "하잉";
value = 123;
type Person = { name: string };
type Employee = { employeeId: number };
type Staff = Person & Employee;
const staff: Staff = {
name: "박지성",
employeeId: 217,
};
TypeScript 공식 문서에서는 인터페이스와 타입을 사용하는 상황에 대해 다음과 같은 가이드를 제시함:
- 객체 지향적인 구조가 필요한 경우: 클래스와 연동하여 사용할 때는 인터페이스를 사용하는 것이 좋음.
- 확장과 병합이 필요할 때: 여러 선언을 병합하여 하나의 인터페이스로 만들 수 있으므로, 확장이 예상되는 경우 인터페이스를 권장함.
interface Animal {
name: string;
}
interface Animal {
age: number;
}
const animal: Animal = { name: "강아지", age: 3 };
- 유연한 타입 조합이 필요한 경우: 유니온 타입이나 인터섹션 타입을 사용할 때는 타입을 사용하는 것이 적합함.
- 동적이고 복합적인 타입을 다룰 때: 함수형 프로그래밍 스타일을 따르거나, 다양한 데이터 구조를 유연하게 정의할 때는 타입을 사용하는 것이 더 적합함.
type ID = string | number;
인터페이스와 타입은 TypeScript에서 각각의 강점과 용도를 가지고 있음. 객체지향적인 구조를 정의하고 확장이 필요한 경우에는 인터페이스가 적합하며, 유연하고 복잡한 데이터 구조를 정의할 때는 타입을 사용하는 것이 좋음.
권장사항 요약:
extends
와 &
의 차이점
- 위 내용에서 타입에서 인터섹션을 사용하여 속성 확장이 가능하다고 언급하였음. interface도 객체지향적으로
extends
상속이 가능함.
extends
: 상속을 의미. 타입의 인터섹션과 기능은 같지만 개념이 다름.
extends
는 상속 개념으로, 인터페이스 확장을 위해 사용됨.&
는 여러 타입을 결합하는 방식으로, 모든 속성을 합쳐 새로운 타입을 만듬.
extends 예시:
interface Person {
name: string;
age: number;
}
interface Employee extends Person {
salary: number;
}
Omit
사용 방법
Omit
은 타입이나 인터페이스에서 특정 속성을 제외하고 싶을 때 사용됨.
타입에서 Omit
사용:
type Person = { name: string; age: number; address: string; }
type PersonWithoutAddress = Omit<Person, "address">;
인터페이스에서 Omit
사용:
interface Person {
name: string;
age: number;
address: string;
}
interface PersonWithoutAddress extends Omit<Person, "address"> {}
extends
: 상속 개념으로, 인터페이스를 확장할 때 사용.&
: 여러 타입을 합쳐 새로운 타입을 만들 때 사용.Omit
: 특정 속성을 제외한 타입이나 인터페이스를 정의할 때 사용.