객체의 타입을 정의하는 핵심 도구, 인터페이스의 기본부터 확장까지 단계별로 정리합니다.
1. 왜 interface가 필요한가?
object 타입으로 변수를 선언하면 프로퍼티에 접근할 수 없습니다.
let user: object;
user = {
name: 'xx',
age: 30
}
console.log(user.name); // ❌ Error — object 타입에는 name이 없음
이 문제를 해결하기 위해 interface로 객체의 구조를 명확히 정의합니다.
interface User {
name: string;
age: number;
}
let user: User = {
name: 'xx',
age: 30
}
user.age = 10; // ✅ 정상 동작
console.log(user.age); // 10
💡 interface는 객체가 어떤 프로퍼티와 타입을 가져야 하는지 계약(contract)을 정의하는 것입니다. 정의되지 않은 프로퍼티를 추가하려고 하면 에러가 발생합니다.
2. 옵셔널 프로퍼티 (?)
모든 프로퍼티가 항상 필요한 건 아닙니다. ?를 붙이면 해당 프로퍼티는 있어도 되고 없어도 됩니다.
interface User {
name: string;
age: number;
gender?: string; // 있어도 되고 없어도 됨
}
let user: User = {
name: 'xx',
age: 30
// gender 없이도 ✅ OK
}
user.gender = "male"; // ✅ 나중에 추가도 OK
?가 없으면 객체 생성 시 해당 프로퍼티를 반드시 포함해야 하므로, 선택적 데이터에는 옵셔널을 활용하세요.
3. 읽기 전용 프로퍼티 (readonly)
생성 시 값을 할당하면 이후 수정할 수 없는 프로퍼티입니다.
interface User {
name: string;
age: number;
gender?: string;
readonly birthYear: number;
}
let user: User = {
name: 'xx',
age: 30,
birthYear: 2000,
}
user.age = 10; // ✅ OK
user.gender = "male"; // ✅ OK
user.birthYear = 1990; // ❌ Error — 읽기전용 속성이라 할당 불가
💡 생년월일, ID 같은 한번 정해지면 변하지 않는 값에 readonly를 사용하면 실수로 변경하는 것을 방지할 수 있습니다.
4. 인덱스 서명 (Index Signature)
프로퍼티의 이름을 미리 알 수 없지만, 키와 값의 타입 패턴이 일정할 때 사용합니다.
interface User {
name: string;
age: number;
readonly birthYear: number;
[grade: number]: string; // 숫자 키에 문자열 값
}
let user: User = {
name: 'xx',
age: 30,
birthYear: 2000,
1: 'A',
2: 'B'
}
문자열 리터럴 타입과 조합
인덱스 서명의 값을 특정 값으로 제한할 수도 있습니다.
type Score = 'A' | 'B' | 'C' | 'F';
interface User {
name: string;
age: number;
readonly birthYear: number;
[grade: number]: Score; // Score에 정의된 값만 허용
}
let user: User = {
name: 'xx',
age: 30,
birthYear: 2000,
1: 'A',
2: 'G' // ❌ Error — 'G'는 Score 타입에 없음
}
5. 인터페이스로 함수 타입 정의
인터페이스는 객체뿐 아니라 함수의 형태도 정의할 수 있습니다.
interface Add {
(num1: number, num2: number): number;
}
const add: Add = function(x, y) {
return x + y;
}
add(10, 20); // 30
interface IsAdult {
(age: number): boolean;
}
const a: IsAdult = (age) => {
return age > 19;
}
a(33); // true
💡 함수 인터페이스를 사용하면 매개변수 이름은 달라도 됩니다. 중요한 건 매개변수의 타입과 반환 타입이 일치하는 것입니다.
6. 인터페이스로 클래스 정의 (implements)
implements 키워드를 사용하면 클래스가 특정 인터페이스의 구조를 반드시 따르도록 강제할 수 있습니다.
interface Car {
color: string;
wheels: number;
start(): void;
}
class Bmw implements Car {
color;
wheels = 4;
constructor(c: string) {
this.color = c;
}
start() {
console.log('go...');
}
}
const b = new Bmw('green');
console.log(b); // Bmw { wheels: 4, color: 'green' }
b.start(); // "go..."
Car 인터페이스에 정의된 color, wheels, start()를 모두 구현하지 않으면 컴파일 에러가 발생합니다.
7. 인터페이스 확장 (extends)
기존 인터페이스를 상속받아 새로운 프로퍼티를 추가할 수 있습니다.
interface Car {
color: string;
wheels: number;
start(): void;
}
interface Benz extends Car {
door: number;
stop(): void;
}
const benz: Benz = {
// Car에서 상속받은 프로퍼티
color: 'g',
wheels: 4,
start() {
console.log('go...');
},
// Benz에서 추가된 프로퍼티
door: 5,
stop() {
console.log('stop..');
}
}
여러 인터페이스 동시 확장
쉼표(,)로 구분하여 여러 인터페이스를 동시에 상속받을 수 있습니다.
interface Car {
color: string;
wheels: number;
start(): void;
}
interface Toy {
name: string;
}
interface ToyCar extends Car, Toy {
price: number;
}
ToyCar는 Car의 color, wheels, start()와 Toy의 name, 그리고 자체 프로퍼티인 price를 모두 가져야 합니다.
한눈에 보기
기능 문법 설명
| 기본 정의 | interface User { name: string; } | 객체 구조 정의 |
| 옵셔널 | gender?: string | 선택적 프로퍼티 |
| 읽기 전용 | readonly birthYear: number | 수정 불가 |
| 인덱스 서명 | [key: number]: string | 동적 키-값 패턴 |
| 함수 타입 | (x: number): boolean | 함수 형태 정의 |
| 클래스 구현 | class Bmw implements Car | 클래스에 인터페이스 적용 |
| 확장 | interface Benz extends Car | 인터페이스 상속 |
| 다중 확장 | interface ToyCar extends Car, Toy | 여러 인터페이스 상속 |
'프론트' 카테고리의 다른 글
| TypeScript 리터럴 · 유니온 · 교차 타입 정리 (0) | 2026.04.11 |
|---|---|
| TypeScript 함수 타입 (0) | 2026.04.11 |
| TypeScript 기초 타입 정리 (0) | 2026.04.11 |
| 타입스크립트를 사용하는 이유 (0) | 2026.04.11 |
| JavaScript 한국어 조사 자동 선택 함수 (0) | 2026.02.10 |