
= 상호간에 약속된 규칙
인터페이스란 타입 별칭과 동일하게 타입에 이름을 지어주는 또 다른 문법이며, 객체 타입을 정의하는데 특화된 문법이다 상속, 합침 등의 특수한 기능을 제공한다.
이렇게 정의한 인터페이스를 타입 주석과 함께 사용해 변수의 타입을 정의할 수 있다.
interface Person {
name: string;
age: number;
}
const person: Person = {
name: "챙",
age : 27
};
인터페이스에서도 동일한 방법으로 선택적 프로퍼티, 읽기전용 프로퍼티 설정이 가능
interface Person {
name: string;
age?: number;
}
const person: Person = {
name: "챙",
// age: 27,
};
interface Person {
readonly name: string;
age?: number;
}
const person: Person = {
name: "챙",
// age: 27,
};
person.name = '홍길동' // ❌
함수 타입 표현식, 호출 시그니처 둘 다 가능하다.
// 함수 타입 표현식
interface Person {
readonly name: string;
age?: number;
sayHi: () => void;
}
// 호출 시그니처
interface Person {
readonly name: string;
age?: number;
sayHi(): void;
}
함수 타입 표현식으로 메서드의 타입을 정의하면 메서드의 오버로딩 구현이 불가능
interface Person {
readonly name: string;
age?: number;
sayHi: () => void;
sayHi: (a: number, b: number) => void; // ❌
}
호출 시그니처를 이용해 메서드의 타입을 정의하면 오버로딩 구현이 가능
interface Person {
readonly name: string;
age?: number;
sayHi(): void;
sayHi(a: number): void;
sayHi(a: number, b: number): void;
}
interface Func2 {
(a: number): string;
b: boolean;
}
const func: Func2 = (a) => "hello";
func.b = true;
타입 별칭에서는 다음과 같이 Union이나 Intersection 타입을 정의할 수 있었던 반면 인터페이스에서는 할 수 없다.
type Type1 = number | string;
type Type2 = number & string;
interface Person {
name: string;
age: number;
} | number // ❌
따라서 인터페이스로 만든 타입을 Union 또는 Intersection으로 이용해야 한다면 다음과 같이 타입 별칭과 함께 사용하거나 타입 주석에서 직접 사용해야 한다.
type Type1 = number | string | Person;
type PersonWithLanguage = Person & { language: string };
const person: PersonWithLanguage = {
name: "챙",
age: 27,
language: "Korean" // 추가된 프로퍼티
};
하나의 인터페이스를 다른 인터페이스들이 상속받아 중복된 프로퍼티를 정의하지 않도록 도와주는 문법이다.
interface Animal {
name: string;
age: number;
}
interface Dog {
name: string;
age: number;
isBark: boolean;
}
interface Cat {
name: string;
age: number;
isScratch: boolean;
}
interface Chicken {
name: string;
age: number;
isFly: boolean;
}
위와같이 중복되는 타입들 가진 객체들을 Animal을 기반으로 확장할 수 있도록 할 수 있다.
interface Animal {
name: string;
color: string;
}
interface Dog extends Animal {
breed: string;
}
interface Cat extends Animal {
isScratch: boolean;
}
interface Chicken extends Animal {
isFly: boolean;
}
interface 타입이름 extends 확장_할_타입이름 형태로 extends 뒤에 확장할 타입의 이름을 정의하면 해당 타입에 정의된 모든 프로퍼티를 다 가지고 오게 된다. 따라서 Dog, Cat, Chicken 타입은 모두 Animal 타입을 확장하는 타입이기 때문에 name, color 프로퍼티를 갖게 된다.
interface Animal {
name: string;
color: string;
}
interface Dog extends Animal {
breed: string;
}
(...)
const dog: Dog = {
name: "돌돌이",
color: "brown",
breed: "진도",
};
확장 대상 타입인 Animal은 Dog 타입의 슈퍼타입이 된다.
다음과 같이 확장과 동시에 프로퍼티의 타입을 재 정의 하는 것 또한 가능하다.
interface Animal{
name:string;
color:string;
}
interface Dog extends Animal{
name:"doldol";// 타입 재 정의
breed:string;
}
Dog 타입은 Animal 타입을 확장하며 동시에 name 프로퍼티의 타입을 String 타입에서 “doldol” String Literal 타입으로 재정의가 가능하다. 이렇게 확장받는 타입에서 프로퍼티의 타입을 재정의 할 수 있다.
그런데 한가지 주의할 점은 프로퍼티를 재 정의할 때 원본 타입을 A 재 정의된 타입을 B라고 하면 반드시 A가 B의 슈퍼 타입이 되도록 재정의 해야한다.
따라서 다음과 같이 name을 Number 타입으로 재 정의 하는 것은 불가능하다.
interface Animal{
name:string;
color:string;
}
interface Dog extends Animal{
name:number;// ❌
breed:string;
}
그 이유는 Dog 타입이 Animal 타입을 확장한 다는 것은 Animal 타입의 서브타입이 된다는 의미이다.
그런데 name 프로퍼티를 Number 타입으로 재 정의 해 버리면 이제는 Dog는 Animal의 서브 타입이 아니게 되므로 이런 재 정의는 할 수 없다.
프로퍼티는 서브타입으로만 재 정의 가능하다.
참고로 인터페이스는 인터페이스 뿐만 아니라 타입 별칭으로 정의된 객체도 확장할 수 있다.
type Animal={
name:string;
color:string;
};
interface Dog extends Animal{
breed:string;
}
또 여러개의 인터페이스를 확장하는 것 또한 가능하다.
interface DogCat extends Dog, Cat{}
const dogCat: DogCat={
name:"",
color:"",
breed:"",
isScratch:true,
};
타입 별칭은 동일한 스코프 내에 중복된 이름으로 선언할 수 없는 반면 인터페이스는 가능하다.
중복된 이름의 인터페이스 선언은 결국 모두 하나로 합쳐진다.
type Person = {
name:string;
};
type Person = { // ❌
age:number;
};
interface Person {
name:string;
}
interface Person { // ✅
age:number;
}
위 코드에 선언한 Person 인터페이스들을 결국 합쳐져 다음과 같은 인터페이스가 된다.
interface Person {
name:string;
age:number;
}
이렇게 동일한 이름의 인터페이스들이 합쳐지는 것을 선언 합침(Declaration Merging)이라고 부른다.
따라서 다음과 같이 사용할 수 있다.
interface Person {
name: string;
}
interface Person {
age: number;
}
const person: Person = {
name: "이정환",
age: 27,
};
그런데 만약 다음과 같이 동일한 이름의 인터페이스들이 동일한 이름의 프로퍼티를 서로 다른 타입으로 정의한다면 오류가 발생한다. 서브타입도 안되고, 무조건 타입이 같아야 한다.
interface Person {
name: string;
}
interface Person {
name: number; // ❌
name: "hello" // ❌
age: number;
}
첫번째 Person에서는 name 프로퍼티의 타입을 string 으로 두번째 Person 에서는 name 프로퍼티의 타입을 number 타입으로 정의하면 에러가 난다.
이렇게 동일한 프로퍼티의 타입을 다르게 정의한 상황을 충돌이라고 표현하며 선언 합침에서 이런 충돌은 허용되지 않는다.
모듈보강시 많이 사용함
// 라이브러리에서 선언한 인터페이스 interface Lib { a: number; b: number; } // 사용자가 추가하고 싶은 타입을 합침 interface Lib { c: string; } // 사용자의 커스텀 된 c타입 까지 함께 사용 가능 const lib :Lib = { a: 1, b: 2, c: "hello" }
둘 다 객체의 타입을 정의할 수 있다는 점은 같지만, 확장성과 사용 목적에서 결정적인 차이가 있다.
| 특징 | 인터페이스 (Interface) | 타입 별칭 (Type Alias) |
|---|---|---|
| 정의 대상 | 객체의 구조를 정의하는 데 특화 | 객체, 원시값, 유니온, 튜플 등 모든 타입 가능 |
| 확장 방식 | extends 키워드 사용 (상속 개념) | & (Intersection) 연산자 사용 |
| 선언 합침 | 가능 (같은 이름으로 선언 시 자동 병합) | 불가능 (중복 선언 시 에러 발생) |
객체가 아닌 단순한 값, 튜플, 유니온 타입을 정의할 때 (type ID = string | number)
유틸리티 타입이나 맵드 타입 등 복잡한 타입 변환이 필요할 때
