
인터페이스(interface)는 객체, 함수, 클래스의 구조를 표현하는 약속이며 일반적으로 변수, 함수, 클래스에 타입 체크를 위해 사용된다.
인터페이스를 이용해 객체와 함수가 지정된 형태를 갖도록 규정하고 통제할 수 있으며, 인터페이스를 지정하는 방법은 객체에 대한 새로운 타입을 생성하는 방법과 유사하다.
직접 인스턴스를 생성할 수 없고 모든 메서드가 추상 메서드이다. 추상 클래스의 추상 메서드와 달리 abstract 키워드는 사용할 수 없다.
아래 예제를 보면 인터페이스를 인자로 받아 사용할 때 항상 인터페이스의 속성 갯수와 인자로 받는 객체의 속성 갯수를 일치시키지 않아도 되는 것을 확인할 수 있다. 즉, 인터페이스에 정의된 속성, 타입의 조건만 만족한다면 객체의 속성 갯수가 더 많아도 상관 없다는 뜻이다. 또한, 인터페이스에 선언된 속성 순서를 지키지 않아도 된다.
단, 정의한 프로퍼티 값을 누락하거나 정의하지 않는 값을 인수로 전달 시 컴파일 에러가 발생한다.
function logAge(obj : { age : number }) {
console.log(obj.age); // 28
}
let person = { name : 'Capt', age : 28 };
logAge(person);
// 위 코드를 interface로 변환
interface Person {
age: number;
}
function logAge(obj : Person) {
console.log(obj.age); // 28
}
let person = { name : 'Capt', age : 28 };
logAge(person);
인터페이스를 사용할 때 인터페이스에 정의되어 있는 속성 또는 메서드를 반드시 사용하지 않고, 필요에 따라 선택적으로 사용할 수 있다. 이 경우 옵션(Optional) 속성 설정을 통해 사용자가 선택적으로 사용하게 설정할 수 있다.
속성 이름 뒤에 ?를 붙이면 옵션 속성이 되며, 이 옵션을 선택적으로 사용할 수 있게 된다.
아래 예제를 보면 brewBeer() 함수에서 Beer 인터페이스를 인자의 타입으로 선언했음에도 불구하고, 인자로 넘긴 객체에는 hop 속성이 없다. 왜냐하면 hop을 옵션 속성으로 선언했기 때문이다.
interface CraftBeer {
name: string;
hop?: number;
}
function brewBeer(beer : CraftBeer) {
console.log(beer.name); // Saporo
}
let myBeer = { name : 'Saporo' };
brewBeer(myBeer);
읽기 전용 속성은 인터페이스로 객체를 처음 생성할 때만 값을 할당하고 그 이후에는 변경할 수 없는 속성을 의미한다.
속성 이름 앞에 readonly를 넣어 읽기 전용 속성으로 설정할 수 있다.
interface CraftBeer {
readonly brand: string;
}
let myBeer : CraftBeer = { brand: 'Belgian Monk' };
// 인터페이스로 객체를 선언하고 나서 수정하려고 하면 오류가 난다.
myBeer.brand = 'Korean Carpenter'; // error!
ReadonlyArray<T> 타입을 사용하면 읽기 전용 배열을 생성할 수 있다. 아래의 배열을 ReadonlyArray로 선언하면 배열의 내용을 변경할 수 없다. (선언하는 시점에만 값을 정의할 수 있다.)let arr : ReadonlyArray<number> = [1,2,3];
arr.splice(0,1); // error
arr.push(4); // error
arr[0] = 100; // error
인터페이스를 같은 이름으로 중복 정의하면 인터페이스에 선언된 속성들은 모두 병합되어 하나의 인터페이스에 쓰인 것과 같아진다.
아래 예제에서는 IPerson이라는 인터페이스가 2개 있다. name 속성은 중복이지만 age와 tel은 중복되지 않는다. 이 속성들은 병합되어 IPerson은 name, tel, age 속성을 정의한 인터페이스로 작동한다.
interface IPerson {
name: string;
age: number;
}
interface IPerson {
name: string;
tel: string;
}
let p5: IPerson = {
name: "홍길동",
tel: "010-111-2222",
age: 20,
};
&)을 이용해 확장하고, 인터페이스는 상속(extends)을 이용해 확장한다.// IEmployee 인터페이스에서 Iperson2 인터페이스를 확장하고 있다.
interface IPerson2 {
name: string;
age: number;
}
interface IEmployee extends IPerson2 {
employeeId: string;
dept: string;
}
let e1: IEmployee = {
employeeId: "E001",
dept: "회계팀",
name: "홍길동",
age: 20,
};
interface User {
name: string;
}
// User 인터페이스를 두 번 선언했지만, name과 age가 모두 있는 단일 User 타입으로 병합된다.
interface User {
age: number;
}
const user: User = {
name: "Alice",
age: 30,
};
extends 키워드를 통해 다른 인터페이스를 상속받아 확장할 수 있다.interface Person {
name: string;
}
// Employee는 Person을 상속받아 name에 더해 employeeId를 가진다.
interface Employee extends Person {
employeeId: number;
}
const employee: Employee = {
name: "Bob",
employeeId: 1234,
};
type Product = {
id: number;
title: string;
};
// type Product는 한 번만 선언 가능하며 같은 이름으로 다시 선언하면 에러가 발생한다.
// Duplicate identifier 'Product'.
// type Product = {
// price: number;
// };
& 연산자를 통해 교차 타입(intersection type)으로 확장할 수 있지만, 인터페이스처럼 선언적 병합은 지원하지 않는다.type ProductBase = {
id: number;
title: string;
};
type ProductWithPrice = ProductBase & {
price: number;
};
const p: ProductWithPrice = {
id: 1,
title: "Notebook",
price: 9.99,
};
// 유니언 타입을 interface로 선언할 수 없다.
interface NameOrID = { name: string } | { id: number };
type은 유니언(union), 튜플(tuple), 원시 타입(alias)까지 모두 정의할 수 있어 복합 타입 구성에 강하다.
예를 들어, ID라는 타입을 정의할 때, number 또는 string 타입을 받을 수 있게 하여 유연성을 제공할 수 있다. 또한, 튜플이나 조건에 맞는 원시 타입을 포함하는 타입을 정의할 수 있다.
type ID = number | string; // 유니언 타입
type Point = [number, number]; // 튜플
type NameOrID = { name: string } | { id: number }; // 유니언 타입
const a: ID = 42;
const b: ID = "xyz";
const pt: Point = [10, 20];
// interface로는 조건부 타입을 선언할 수 없다.
// interface ElementType<T> = T extends (infer U)[] ? U : T;
// interface로는 매핑된 타입을 선언할 수 없다.
// interface ReadonlyProps<T> {
// readonly [P in keyof T]: T[P];
// }
// 1) 조건부 타입 (Conditional Type)
type ElementType<T> = T extends (infer U)[] ? U : T;
type A = ElementType<string[]>; // string
type B = ElementType<number>; // number
// 2) 매핑된 타입 (Mapped Type)
// 모든 프로퍼티를 읽기 전용으로 바꾸는 ReadonlyProps
type ReadonlyProps<T> = { readonly [P in keyof T]: T[P] };
// 모든 프로퍼티를 선택적(optional)으로 바꾸는 PartialProps
type PartialProps<T> = { [P in keyof T]?: T[P] };
// K 집합에 대해 동일한 타입 T를 매핑하는 Record 타입
type MyRecord<K extends keyof any, T> = { [P in K]: T };
interface User { id: number; name: string; }
// ReadonlyProps 예시
type ReadonlyUser = ReadonlyProps<User>;
// { readonly id: number; readonly name: string; }
// PartialProps 예시
type PartialUser = PartialProps<User>;
// { id?: number; name?: string; }
// Record 예시
type RoleMap = MyRecord<'admin' | 'user', User>;
// { admin: User; user: User; }