2편. 객체 설계의 핵심, Interface vs Type Alias 완벽 정리와 타입 추론 원리
📚 목차
1. 인터페이스 (Interface): 객체 타입 정의의 기본
2. type vs interface: 언제 무엇을 사용해야 할까요?
3. 객체 타입 심화: 유연하고 안전한 구조 만들기
4. 타입 추론 (Type Inference) 이해하기: 자동으로 타입 유추

📂 [GitHub 예시 코드 보러가기] (https://github.com/cericube/nodejs-tutorials) /TypeScript
1. 인터페이스 (Interface): 객체 타입 정의의 기본
JavaScript의 객체는 매우 유연하여 다양한 속성과 구조를 가질 수 있지만, 반대로 말하면 어떤 속성이 존재하는지 사전에 알기 어렵고, 실수로 잘못된 속성을 써도 런타임까지 오류를 알기 어렵습니다.
TypeScript에서는 이러한 문제를 해결하기 위해 인터페이스(Interface)라는 기능을 제공합니다.
✔️ 인터페이스란?
인터페이스는 객체가 가져야 할 속성(property)과 메서드(method)의 이름과 타입을 사전에 정의하는 일종의 설계도입니다.
▸ 객체에 어떤 속성이 있어야 하는지 명확히 규정할 수 있습니다.
▸ 자동 완성, 타입 체크, 문서화 등에 큰 도움이 됩니다.
▸ 클래스(class)와도 함께 사용될 수 있습니다
🔷 인터페이스 정의 및 사용법
// 'User'라는 이름의 인터페이스를 정의합니다.
interface User {
id: number;
name: string;
email: string;
isActive: boolean;
// 선택적 속성: 있어도 되고 없어도 되는 값
phoneNumber?: string;
// 메서드 정의: 함수 타입도 포함 가능
greet(): string;
}
// 이 객체는 반드시 User 인터페이스의 구조를 따라야 합니다.
const user1: User = {
id: 1,
name: "김민준",
email: "minjun.kim@example.com",
isActive: true,
greet() {
return `안녕하세요, ${this.name}입니다.`;
}
};
console.log(user1.greet()); // 출력: 안녕하세요, 김민준입니다.
▸ id, name, email, isActive: 필수 속성
▸ phoneNumber: 선택적(optional) 속성 → 있어도 되고 없어도 됩니다
▸ greet(): 문자열을 반환하는 메서드 정의
✔️ 오류 예시: 속성 누락 또는 타입 불일치
const user2: User = {
id: 2,
name: "이서연",
email: "seoyeon@example.com",
// isActive가 누락되었고 greet 메서드도 없음 → 오류 발생
// Property 'isActive' is missing in type ...
greet() {
return "안녕하세요!";
}
};
TypeScript는 컴파일 단계에서 User 인터페이스 정의와 실제 객체 구조를 자동으로 비교하여 누락된 속성이나 잘못된 타입을 미리 알려줍니다.
🔷 인터페이스 확장 (Extending Interfaces)
인터페이스는 다른 인터페이스를 확장(extend) 할 수 있습니다.
이렇게 하면 기존 인터페이스의 속성은 그대로 유지하면서 새로운 속성을 추가할 수 있습니다.
// User 인터페이스를 확장한 AdminUser
interface AdminUser extends User {
role: "admin" | "super-admin"; // 역할은 두 가지 중 하나
permissions: string[]; // 권한 목록
}
const admin: AdminUser = {
id: 100,
name: "홍길동",
email: "jihoon.park@example.com",
isActive: true,
role: "admin",
permissions: ["read", "write", "delete"],
greet() {
return `관리자 ${this.name}입니다.`;
}
};
console.log(admin.greet()); // 출력: 관리자 박지훈입니다.
확장의 장점
▸ 코드 재사용: 공통 속성을 별도로 정의해 두고 여러 타입에 공유 가능
▸ 유지보수 용이: 수정할 때 한 군데만 고치면 됨
▸ 의미 있는 타입 분리: 일반 사용자와 관리자 같은 역할 구분에 효과적
2. type vs interface: 언제 무엇을 사용해야 할까요?
TypeScript를 처음 접하신 분들께서 가장 자주 헷갈려하시는 부분 중 하나가 바로 type 키워드를 이용한 타입 별칭(Type Alias)과 interface를 이용한 인터페이스 정의입니다.
두 문법 모두 객체의 구조를 정의할 수 있다는 공통점이 있지만, 그 용도와 동작 방식에는 중요한 차이점들이 존재합니다.
| 항목 | interface | type |
| 확장(상속) 방식 | extends 키워드를 사용하여 확장 | & 연산자(인터섹션)를 사용하여 결합 |
| 정의할 수 있는 대상 | 객체의 모양(structure) 정의에 특화 | 객체 외에도 기본 타입, 유니언, 튜플 등 다양한 타입 정의 가능 |
| 선언 병합 (Declaration Merging) |
동일한 이름의 interface를 여러 번 선언하면 자동으로 병합됨 | type은 동일한 이름으로 여러 번 선언 시 오류 발생 |
| 공식 문서 권장 용도 | 객체 구조를 정의할 때 추천(라이브러리, 클래스, API 응답 등) | 유니언 타입, 튜플, 복합 타입 등을 정의할 때 추천 |
▸ 객체의 구조를 정의할 때는 interface를 사용하는 것이 일반적입니다.
▸ 특히 확장성(extends)과 선언 병합(declaration merging)이 가능하므로 라이브러리 타입 정의나 객체 지향 설계에 적합합니다.
▸ 반면, type은 유니언 타입("A" | "B"), 튜플, 함수 타입, 기본 타입 조합 등 보다 다양하고 복합적인 타입 정의에 적합합니다.
| 상황 | 추천 |
| 객체 구조를 정의해야 할 때 | interface ✅ |
| 타입 확장을 자주 해야 할 때 | interface ✅ |
| 유니언, 튜플, 복합 타입이 필요한 경우 | type ✅ |
| 기본 타입 또는 다른 타입을 조합할 때 | type ✅ |
| 동일 이름으로 병합이 필요한 경우 | interface만 가능 |
✔️ 예제 1: type으로 유니언 타입 정의하기
type은 이렇게 문자열 리터럴 유니언 타입을 선언할 때 매우 유용합니다.
// 상태 값을 제한된 문자열로 표현
type Status = "pending" | "success" | "error";
// 해당 타입을 변수에 적용
let currentStatus: Status = "success";
// 잘못된 값은 오류 발생
// currentStatus = "done"; // ❌ Error: Type '"done"' is not assignable to type 'Status'.
✔️ 예제 2: interface로 객체 타입 정의하기
interface는 객체의 구조를 명확하게 정의하고, 필요한 경우 선택적 속성도 지정할 수 있어 실무 API 응답 타입 등에 많이 사용됩니다.
interface Response {
status: "success" | "error";
data: any;
message?: string; // 선택적 프로퍼티
}
const res1: Response = {
status: "success",
data: { id: 1, name: "Jane" }
};
const res2: Response = {
status: "error",
data: null,
message: "요청에 실패했습니다."
};
✔️ 예제 3: interface 확장 vs type 확장
두 방식 모두 유사하게 확장 가능하지만, interface는 extends 문법을 사용하고, type은 & 연산자(intersection)를 통해 확장합니다.
// interface 확장
interface Animal {
name: string;
}
interface Dog extends Animal {
breed: string;
}
const myDog: Dog = {
name: "Coco",
breed: "Poodle"
};
// type 확장 (인터섹션 방식)
type Animal = { name: string };
type Dog = Animal & { breed: string };
const myDog: Dog = {
name: "Coco",
breed: "Poodle"
};
✔️ 예제 4: 선언 병합 차이점
// interface는 같은 이름으로 선언 가능하며 병합됨
interface User {
name: string;
}
interface User {
age: number;
}
// 병합된 결과:
const user: User = {
name: "Jane",
age: 30
};
// type은 같은 이름으로 다시 선언하면 오류 발생
type Admin = {
role: string;
};
// 아래는 오류 발생
// type Admin = { level: number }; // ❌ Duplicate identifier 'Admin'
3. 객체 타입 심화: 유연하고 안전한 구조 만들기
TypeScript를 사용하면 객체의 구조를 훨씬 정밀하게 제어할 수 있습니다.
▸ 어떤 속성은 선택적으로 정의할 수 있고,
▸ 어떤 속성은 생성 후 절대 수정할 수 없도록 만들 수 있습니다.
▸ 경우에 따라서는 컴파일러보다 개발자가 타입을 더 잘 알고 있는 상황도 있기 때문에, 타입 단언(Type Assertion)을 통해 타입을 직접 지정해 줄 수도 있습니다.
▸ 추가로, 실무에서 자주 사용하는 계산된 프로퍼티(Computed Property)를 이용하면 키 이름이 동적으로 결정되는 객체도 타입 안전하게 다룰 수 있습니다.
| 개념 | 설명 | 키워드 |
| 선택적 속성 | 있어도 되고 없어도 되는 속성 | ? |
| 읽기 전용 속성 | 생성 후 변경이 불가능한 속성 | readonly |
| 계산된 프로퍼티 | 표현식 결과를 키 이름으로 사용하는 속성 | [expr] |
| 타입 단언 | 개발자가 직접 타입을 지정 (확신이 있을 때) | as |
🔷 선택적 속성 (Optional Properties)
객체의 모든 속성이 항상 필수일 필요는 없습니다.
TypeScript에서는 속성 이름 뒤에 ?를 붙이면 해당 속성을 선택적 속성(optional property) 으로 만들 수 있습니다.
interface Profile {
nickname: string; // 필수 속성
age?: number; // 선택적 속성
}
const profile1: Profile = {
nickname: "TypeScriptLover",
}; // ✅ age가 없어도 오류 없음
const profile2: Profile = {
nickname: "JSExpert",
age: 30,
}; // ✅ age가 있어도 문제 없음
✔️ 선택적 속성과 number | undefined의 차이
interface A {
age?: number;
}
//→ age 라는 키 자체가 없어도 된다
//→ {}도 A 타입으로 허용
const a1: A = {}; // ✅ OK
const a2: A = { age: 10 }; // ✅ OK
interface B {
age: number | undefined;
}
//age 키는 항상 존재해야 한다
//→ 값만 undefined일 수 있음
//→ { age: undefined }는 허용되지만, {}는 B 타입이 될 수 없음
const b1: B = { age: undefined }; // ✅ OK
// const b2: B = {}; // ❌ Property 'age' is missing
🔷 읽기 전용 속성 (readonly)
readonly 키워드를 사용하면 해당 속성은 객체 생성 이후에는 값을 변경할 수 없습니다.
이는 데이터의 불변성(immutability)을 보장하여 예기치 않은 값 변경으로 인한 오류를 방지할 수 있습니다.
interface Point {
readonly x: number;
readonly y: number;
}
const p: Point = { x: 10, y: 20 };
// p.x = 5; // ❌ 오류 발생! Cannot assign to 'x' because it is a read-only property.
✔️ const vs readonly 구분하기
두 키워드는 자주 헷갈리지만, 의미하는 대상이 다릅니다.
const p: Point = { x: 10, y: 20 };
// p = { x: 0, y: 0 }; // ❌ const: 변수 자체 재할당 불가
// p.x = 5; // ❌ readonly: 프로퍼티 값 변경 불가
interface Config {
apiBaseUrl: string;
timeoutMs: number;
}
// 전체를 읽기 전용으로 만들고 싶을 때
const config: Readonly<Config> = {
apiBaseUrl: "https://api.example.com",
timeoutMs: 5000,
};
// config.timeoutMs = 3000; // ❌ 읽기 전용이라 수정 불가
🔷 계산된 프로퍼티 (Computed Property)와 동적 키
실무에서는 키 이름이 코드 실행 시점에 결정되는 객체도 자주 등장합니다.
이럴 때 JavaScript/TypeScript는 계산된 프로퍼티 이름(computed property name) 문법을 제공합니다.
const fieldName = "email";
const user = {
name: "Alice",
[fieldName]: "alice@example.com", // => "email": "alice@example.com"
};
console.log(user.email); // "alice@example.com"
console.log(user["email"]); // "alice@example.com"
▸ [fieldName] 부분이 계산된 프로퍼티 이름입니다.
▸ fieldName 값이 "email"이므로 최종 결과는 { name: "Alice", email: "..." }와 동일합니다.
🔷 Type Assertion (as 키워드)
TypeScript의 타입 시스템은 강력하지만, 때로는 개발자가 타입에 대해 컴파일러보다 더 잘 알고 있는 상황이 존재합니다.
이럴 경우, 타입 단언(Type Assertion)을 통해 컴파일러에게 "이 값은 내가 보장하는 이 타입이야"라고 명시할 수 있습니다.
가장 일반적인 문법은 as 키워드를 사용하는 것입니다
type CanvasLike = {
id: string;
};
const unknownValue: unknown = { id: 'main_canvas' };
// Type Assertion: unknown → CanvasLike
const canvasLike = unknownValue as CanvasLike;
console.log('canvasLike.id:', canvasLike.id);
단언은 타입 검사를 우회하기 때문에, 절대로 무분별하게 사용해서는 안 됩니다.
✔️ 타입 단언 사용 시 주의점
타입 단언은 타입 검사를 우회하는 기능입니다.
실제로는 <canvas>가 아닌 다른 요소가 들어오거나, 아예 요소가 없는데도 억지로 HTMLCanvasElement라고 단언해 버리면, 런타임에서 치명적인 오류가 발생할 수 있습니다.
가능하다면 다음과 같이 런타임 체크(타입 좁히기)를 먼저 고려하는 것이 더 안전합니다.
const el = document.getElementById("main_canvas");
if (el instanceof HTMLCanvasElement) {
// 여기서는 TypeScript가 el을 HTMLCanvasElement로 인식합니다.
const ctx = el.getContext("2d");
ctx?.fillRect(0, 0, 100, 100);
}
▸ type(타입 별칭)이나 interface는 전부 “타입스크립트 전용 개념”이라서 instanceof에 쓸 수 없습니다.
✔️ instanceof를 쓸 수 있는 경우
▸ class로 만든 인스턴스 (가장 일반적)
▸ 전통적인 생성자 함수로 만든 인스턴스
▸ Array, Date, RegExp, Error 등 내장 생성자 인스턴스
▸ Symbol.hasInstance를 구현한 함수/클래스에 대해 커스텀 체크
4. 타입 추론 (Type Inference) 이해하기: 자동으로 타입 유추
TypeScript의 가장 큰 장점 중 하나는 타입 추론(Type Inference)입니다.
타입 추론이란 개발자가 타입을 명시적으로 선언하지 않아도, TypeScript가 변수의 초기값이나 반환 값 등을 보고 자동으로 적절한 타입을 유추하는 기능입니다.
덕분에 코드가 간결해지고, 타입 안정성도 유지할 수 있습니다.
| 상황 | 타입 추론 가능 여부 | 타입 명시 필요 여부 |
| 변수 선언 + 초기값 있음 | ✅ 가능 | ⛔ 생략 가능 |
| 변수 선언 + 초기값 없음 | ❌ 불가 | ✅ 필요 |
| 함수 반환값 | ✅ 가능 (매개변수에 타입 지정 시) | ⛔ 생략 가능 |
| 함수 매개변수 | ❌ 불가 | ✅ 반드시 필요 |
🔷 타입 추론의 기본 원리
TypeScript는 변수에 초기값이 주어졌을 때, 또는 함수가 값을 반환할 때, 그 값을 기준으로 타입을 자동으로 판단합니다.
✔️ 변수 선언 시의 타입 추론
let favoriteColor: string = "blue";처럼 타입을 명시하지 않아도, TypeScript가 "blue"라는 값을 보고 string 타입으로 이해합니다.
let favoriteColor = "blue";
// ⤷ TypeScript는 "blue"가 문자열이므로 favoriteColor를 `string` 타입으로 추론합니다.
favoriteColor = "red"; // ✅ OK
// favoriteColor = 10; // ❌ 오류: 'number'는 'string'에 할당할 수 없습니다.
✔️ 함수 반환값의 타입 추론
함수의 매개변수는 타입을 명시했기 때문에, 반환 타입도 명확하게 추론할 수 있습니다.
function calculateSum(a: number, b: number) {
return a + b;
}
let result = calculateSum(5, 3);
// ⤷ TypeScript는 반환되는 값이 숫자이므로, result의 타입을 `number`로 추론합니다.
console.log(result * 2); // ✅ 16 출력
🔷 타입 명시가 필요한 경우
대부분의 경우 타입 추론만으로 충분하지만, 다음과 같은 상황에서는 타입을 명시적으로 지정해 주는 것이 좋습니다
1. 초기값 없이 선언한 변수
let data;
// ⤷ 아무 값도 지정하지 않으면 TypeScript는 `any` 타입으로 추론합니다.
data = 10; // ✅ OK
data = "hello"; // ✅ OK (any는 어떤 타입이든 허용되므로 안전하지 않음)
// 해결방법
let count: number;
// count = "text"; // ❌ 오류: 'string'은 'number'에 할당될 수 없습니다.
2. 함수의 매개변수
함수의 매개변수는 초기값이 없기 때문에, TypeScript는 타입을 자동으로 추론할 수 없습니다.
따라서 반드시 타입을 명시해 주어야 합니다.
// 오류 예시:
// function multiply(a, b) {
// return a * b;
// }
// ❌ 오류: 매개변수 'a'의 타입이 암시적으로 'any'입니다.
function multiply(a: number, b: number) {
return a * b;
}
console.log(multiply(4, 5)); // ✅ 20 출력
※ 게시된 글 및 이미지 중 일부는 AI 도구의 도움을 받아 생성되거나 다듬어졌습니다.
'4.Node.js > JavaScript&TypeScript' 카테고리의 다른 글
| [TypeScript] 4편. Union, Literal, Intersection 타입 이해하기 : typeof, keyof, instanceof (0) | 2025.12.16 |
|---|---|
| [TypeScript] 3편. 배열, 튜플, 함수 타입 이해하기 : 매개변수, 반환 타입, 오버로딩 (0) | 2025.12.15 |
| [TypeScript] 1편. TypeScript 핵심 개념과 기본 타입 이해하기 (0) | 2025.12.10 |
| [JavaScript] 10편. 퍼포먼스 & 메모리 최적화 이해하기 (0) | 2025.12.09 |
| [JavaScript] 9편. 에러 처리 기법 이해하기 (0) | 2025.12.09 |