여러 곳에서 사용자 정보를 다루는 코드를 작성한다고 가정합니다. 어떤 곳에서는 user.name을, 어떤 곳에서는 user.username을 씁니다. 오타가 있어도 런타임 전까지 알기 어렵습니다. Interface는 이 문제를 해결합니다. 객체가 어떤 프로퍼티를 가져야 하는지 한 곳에 정의해두면, 어긋난 사용을 즉시 잡아낼 수 있습니다.
interface User {
name: string;
age: number;
email: string;
}
const user: User = {
name: "철수",
age: 30,
email: "chulsoo@example.com",
};
// 프로퍼티가 빠지면 오류
const incomplete: User = {
name: "영희",
// 오류: 'age', 'email' 프로퍼티가 없습니다
};
?를 붙이면 있어도 되고 없어도 되는 프로퍼티가 됩니다.
interface User {
name: string;
age: number;
bio?: string; // 있어도 되고 없어도 됨
}
const user1: User = { name: "철수", age: 30 }; // 정상
const user2: User = { name: "영희", age: 25, bio: "안녕" }; // 정상
bio를 사용할 때는 undefined일 수 있으므로, 존재 여부를 확인하고 사용합니다.
if (user1.bio) {
console.log(user1.bio.toUpperCase());
}
readonly를 붙이면 최초 할당 이후 변경할 수 없습니다.
interface User {
readonly id: number;
name: string;
}
const user: User = { id: 1, name: "철수" };
user.name = "영희"; // 정상
user.id = 2; // 오류: 읽기 전용 프로퍼티에 할당할 수 없습니다
함수도 프로퍼티로 정의할 수 있습니다.
interface Calculator {
add(a: number, b: number): number;
subtract(a: number, b: number): number;
}
const calc: Calculator = {
add(a, b) { return a + b; },
subtract(a, b) { return a - b; },
};
extends로 다른 Interface를 상속받아 확장합니다.
interface Animal {
name: string;
age: number;
}
interface Dog extends Animal {
breed: string;
}
const dog: Dog = {
name: "뭉치",
age: 3,
breed: "골든 리트리버",
};
여러 Interface를 동시에 확장하는 것도 가능합니다.
interface Flyable {
fly(): void;
}
interface Swimmable {
swim(): void;
}
interface Duck extends Animal, Flyable, Swimmable {
quack(): void;
}
같은 이름의 Interface를 여러 번 선언하면 자동으로 합쳐집니다. type과 다른 Interface만의 특성입니다.
interface User {
name: string;
}
interface User {
age: number;
}
// 두 선언이 합쳐짐
const user: User = {
name: "철수",
age: 30,
};
선언 병합은 외부 라이브러리의 타입을 확장할 때 유용합니다. 라이브러리 소스를 수정하지 않고도 타입을 추가할 수 있습니다.
프로퍼티의 이름을 미리 알 수 없을 때 사용합니다.
interface StringMap {
[key: string]: string;
}
const config: StringMap = {
host: "localhost",
port: "3000",
debug: "true",
};
Interface로 함수의 형태도 정의할 수 있습니다.
interface Formatter {
(value: string, locale: string): string;
}
const formatDate: Formatter = (value, locale) => {
return new Date(value).toLocaleDateString(locale);
};
Interface는 코드 곳곳에서 같은 구조를 강제하는 계약 역할을 합니다. 함수 매개변수, 반환값, 클래스 구현 등 어디서나 Interface를 타입으로 사용하면, 구조가 어긋날 때 컴파일러가 즉시 알려줍니다.