[TypeScript] Interfaces
정의
Interface 는 객체의 shape (형태) 을 정의하는 TypeScript 문법입니다. 필드, 메서드, 인덱스 시그니처, 호출 시그니처를 선언하며, extends 로 상속하고 declaration merging 으로 확장할 수 있습니다.
기본 사용
interface User {
id: number;
name: string;
email: string;
}
const u: User = {id: 1, name: "kim", email: "a@b.com"};
function greet(user: User): string {
return `Hi, ${user.name}`;
}
Optional / Readonly / Index Signature
interface Config {
readonly host: string; // 재할당 금지
port: number;
debug?: boolean; // optional
[key: string]: any; // index signature (임의 키)
}
readonly: shallow readonly. 필드 자체는 재할당 못 하지만 필드가 객체면 그 내부는 변경 가능.?: 필드가 없거나undefined일 수 있음.[key: string]: T: 임의의 문자열 키에 T 값 허용.
함수 시그니처
Method 스타일
interface Calculator {
add(a: number, b: number): number;
subtract(a: number, b: number): number;
}
Function 필드 스타일
interface Calculator {
add: (a: number, b: number) => number;
subtract: (a: number, b: number) => number;
}
두 방식은 대부분 상호 교환. Method 스타일은 strictFunctionTypes 하에서 bivariance (덜 엄격), function 필드는 contravariance (엄격). 대개 함수 필드가 더 안전.
Call signature (전체 객체가 호출 가능)
interface Formatter {
(input: string): string; // 호출 시그니처
prefix: string; // 필드도 함께
}
const f: Formatter = Object.assign(
(s: string) => `[${s}]`,
{prefix: "["}
);
Construct signature
interface Constructor {
new (name: string): Person;
}
function factory(C: Constructor, name: string): Person {
return new C(name);
}
extends (상속)
interface Animal {
name: string;
age: number;
}
interface Dog extends Animal {
breed: string;
bark(): void;
}
const d: Dog = {name: "Rex", age: 3, breed: "Shiba", bark: () => {}};
여러 인터페이스 상속:
interface Swimmer { swim(): void; }
interface Runner { run(): void; }
interface Triathlete extends Swimmer, Runner {
bike(): void;
}
Class implements
interface Greetable {
name: string;
greet(): string;
}
class Person implements Greetable {
constructor(public name: string) {}
greet(): string {
return `Hi, ${this.name}`;
}
}
주의: implements 는 검사만. 실제 필드/메서드는 클래스가 스스로 구현. 상속 (extends class) 과 다름.
Declaration Merging
같은 이름의 interface 를 여러 번 선언하면 자동 병합:
interface Window {
myGlobal: string;
}
// 다른 파일
interface Window {
anotherGlobal: number;
}
// 결과
Window = {myGlobal: string; anotherGlobal: number}
이 특성 덕분에 module augmentation 이 가능. 라이브러리 타입 확장:
// express-extension.d.ts
declare module "express" {
interface Request {
userId?: string;
}
}
// 이제 어디서든
req.userId = "abc";
Type alias 는 이 기능 없음. type X = ... 을 두 번 선언하면 오류.
Interface vs Type Alias
거의 상호 교환. 차이점:
| 기준 | Interface | Type Alias |
|---|---|---|
| Declaration merging | ✓ | ✗ |
| Union / Intersection | Intersection 만 (extends) | 둘 다 (|, &) |
| Primitive alias | ✗ | ✓ (type ID = string) |
| Mapped/Conditional 표현 | ✗ | ✓ |
| 성능 | 살짝 유리 (컴파일러 최적화) | 대부분 무시할 수준 |
관용:
- Object shape 라면 interface (일관성)
- Union, mapped, conditional, primitive alias 면 type
- 라이브러리 사용자에게 확장 가능성을 제공하고 싶으면 interface
- 확장 불가로 봉인하고 싶으면 type
Extending Type Alias
Interface 는 type alias 를 extends 가능:
type Point = {x: number; y: number};
interface Point3D extends Point {
z: number;
}
Type alias 는 & 로 합성:
type Point3D = Point & {z: number};
Generic Interface
interface Repository<T> {
find(id: number): Promise<T | null>;
save(item: T): Promise<void>;
delete(id: number): Promise<void>;
}
class UserRepository implements Repository<User> {
async find(id: number): Promise<User | null> { ... }
async save(user: User): Promise<void> { ... }
async delete(id: number): Promise<void> { ... }
}
자세한 것은 Generics 참조.
Hybrid Type
interface Counter {
(): number; // 함수로 호출
count: number; // 필드
reset(): void; // 메서드
}
function makeCounter(): Counter {
const c: any = () => c.count++;
c.count = 0;
c.reset = () => { c.count = 0 };
return c;
}
jQuery 등 오래된 라이브러리의 API 를 타이핑할 때 유용.
Interface for JSON Schema
interface ApiResponse<T> {
status: "ok" | "error";
data?: T;
error?: string;
}
interface User {
id: number;
email: string;
createdAt: string; // ISO date
}
type UserResponse = ApiResponse<User>;
주의: interface 는 컴파일 시 검사만. 런타임에는 실제 검증 안 함. 외부 데이터는 zod / valibot 같은 런타임 검증기 병용.
import {z} from "zod";
const UserSchema = z.object({
id: z.number(),
email: z.string().email(),
createdAt: z.string(),
});
type User = z.infer<typeof UserSchema>; // 스키마에서 타입 추론
const user = UserSchema.parse(rawJson); // 런타임 검증
Interface 로 함수 오버로드
interface Cake {
(name: string): string;
(name: string, layers: number): string;
(name: string, layers: number, flavor: string): string;
}
const bake: Cake = (name: string, layers?: number, flavor?: string) => {
...
};
bake("chocolate");
bake("vanilla", 3);
bake("carrot", 2, "cinnamon");
excess property check
객체 리터럴을 직접 전달할 때 정의되지 않은 필드를 갖고 있으면 오류:
interface Config {
host: string;
port: number;
}
const c: Config = {host: "localhost", port: 80, debug: true}; // 오류
이 검사를 우회하려면:
- 변수 저장 후 전달 (
const raw = {..., debug: true}; const c: Config = raw;) - Index signature 추가 (
[k: string]: any) - Type assertion (
{...} as Config) - 지양
함정
WARNING
readonly 는 shallow. readonly items: Item[] 이어도 items[0].name = "..." 는 허용. 완전한 immutable 이 필요하면 readonly Item[] + 각 필드도 readonly.
CAUTION
Declaration merging 의 함정. 실수로 같은 이름 interface 를 두 번 정의하면 조용히 merge 되어 예상 밖 동작. 명확한 파일/모듈 스코프.
WARNING
implements 는 상속이 아님. 실제 상속은 extends class. interface implements 는 타입 검사만.
IMPORTANT
Excess property check 는 리터럴에만 작동. 변수를 먼저 만들면 회피 가능하지만 대개 실제 버그 신호.
CAUTION
Structural typing 이므로 이름은 무의미. interface User { name: string } 와 interface Product { name: string } 는 상호 대체 가능. 진짜 nominal 이 필요하면 branded type (type UserId = string & {__brand: "UserId"}).
관련 위키
- TypeScript - 상위 개요
- Type Aliases - 유사 개념
- Generics - Generic interface
- Primitive Types
- Union / Intersection
- Type Narrowing
- Declaration Files - 라이브러리 타입
- JavaScript class - implements
- JavaScript object
이 글의 용어 (9개)
- [Javascript] Classjavascript
- 정의 ES6 는 prototype chain 위의 문법 설탕. C++/Java 스타일 OOP 와 닮았지만 내부는 . 구조 상속 super 의 의미 - : 부모 constructo…
- [Javascript] Objectjavascript
- 정의 JavaScript 의 는 key-value 쌍의 컬렉션. 거의 모든 non-primitive 값이 객체. 함수, 배열, 정규식도 객체. 생성 접근 수정 / 삭제 Objec…
- [TypeScript] Declaration Files (.d.ts)typescript
- 정의 Declaration File ( ) 은 타입 정보만 담은 파일입니다. 실행 코드 없이 함수, 클래스, 변수, 모듈의 타입 시그니처 만 선언합니다. JavaScript 라이…
- [TypeScript] Genericstypescript
- 정의 Generics 는 타입을 파라미터화하는 문법입니다. 함수, 클래스, 인터페이스, type alias 가 재사용 가능 하면서도 타입 안전 하도록 만듭니다. Java 의 ge…
- [TypeScript] Primitive Typestypescript
- 정의 Primitive Types 는 TypeScript 의 가장 기본 타입 계열입니다. JavaScript 의 7가지 primitive 값 + 특수 타입 (void, never…
- [TypeScript] Type Aliasestypescript
- 정의 Type Alias 는 타입에 이름을 붙이는 TypeScript 문법 ( ). Interface 는 객체 shape 만 표현하지만, type alias 는 모든 타입 표현식…
- [TypeScript] Type Narrowing (Type Guards)typescript
- 정의 Type Narrowing 은 TypeScript 컴파일러가 제어 흐름 분석 (control flow analysis) 을 통해 특정 위치에서 값의 타입을 더 좁게 추론하는…
- [TypeScript] Union & Intersection Typestypescript
- 정의 - Union Type ( ): A 이거나 B (또는 둘 다) 인 값 - Intersection Type ( ): A 이면서 B 인 값 두 조합은 TypeScript 의 타…
- TypeScripttypescript
- 정의 TypeScript 는 Microsoft 가 2012년 발표한 JavaScript 의 상위 집합 프로그래밍 언어입니다. 정적 타입 시스템, 인터페이스, 제네릭, enum, …
💬 댓글