본문으로 건너뛰기
김신건의 로그

[TypeScript] Interfaces

· 수정 · 📖 약 2분 · 817자/단어 #typescript #types #interface #oop
TypeScript Interface, TypeScript Interfaces, TS interface, interface declaration, declaration merging, extends interface, TS 인터페이스

정의

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

거의 상호 교환. 차이점:

기준InterfaceType Alias
Declaration merging
Union / IntersectionIntersection 만 (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"}).

관련 위키

이 글의 용어 (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, …

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기