숏컷 코드
interface User {
id: number;
name: string;
}
interface User {
email: string;
role: "admin" | "user";
}병합 규칙
선언 병합에서 먼저 구분하면 좋은 대표 형태는 아래입니다.
- 같은 이름의
interface병합 declare module "pkg"로 패키지 타입 보강declare global로 전역 타입 보강namespace병합으로 정적 멤버 타입 추가
1) 같은 이름의 interface는 병합된다
TypeScript에서는 interface를 같은 이름으로 여러 번 선언하면 멤버가 합쳐집니다.
즉 객체 계약을 여러 위치에서 점진적으로 확장할 수 있습니다.
interface Config {
host: string;
}
interface Config {
timeout: number;
}반대로 type은 같은 이름 재선언이 허용되지 않으므로 이 방식의 병합은 안 됩니다.
2) 서드파티 타입 확장은 module augmentation
라이브러리 타입 정의를 직접 수정하지 않고 프로젝트 바깥에서 확장하려면 declare module "패키지명" 패턴을 씁니다.
import "axios";
declare module "axios" {
interface AxiosRequestConfig {
metadata?: {
requestId?: string;
};
}
}이 패턴은 라이브러리 업데이트를 막지 않으면서 필요한 커스텀 필드를 추가할 수 있게 합니다.
3) 전역 타입 확장은 declare global
Express req.user처럼 전역 네임스페이스 아래 타입을 늘려야 할 때는 declare global이 자주 쓰입니다.
export {};
declare global {
namespace Express {
interface Request {
user?: {
id: number;
};
}
}
}4) 함수나 클래스에 정적 멤버 타입을 붙일 때는 namespace 병합
같은 이름의 namespace를 붙이면 함수나 클래스에 정적 멤버 타입을 덧붙이는 패턴도 가능합니다.
라이브러리 스타일 API에서 가끔 보이는 고전적인 패턴입니다.
function createLogger() {}
namespace createLogger {
export const version = "1.0.0";
}5) 보강 파일은 모듈이어야 하고 tsconfig에 포함돼야 한다
declare global은 파일이 모듈로 인식돼야 제대로 동작합니다. import나 export {}가 없으면 스크립트 파일로 취급되어 의도와 다르게 전역 선언이 새어 나가거나 보강이 먹지 않을 수 있습니다. declare module도 결국 TypeScript 프로그램에 포함된 파일이어야 읽히므로, tsconfig의 include 범위와 파일 위치를 같이 확인하는 편이 안전합니다.
언제 병합이 일어나나
- 같은 프로젝트 내 interface를 확장한다: 선언 병합
- 서드파티 패키지 타입에 필드 추가: module augmentation
- 전역 타입을 확장한다:
declare global - 함수/클래스에 정적 멤버 타입 추가: namespace 병합
- 파일이 모듈인지와 tsconfig 포함 여부를 같이 확인
주의할 점
declare global을 쓰는 보강 파일은 모듈이어야 합니다. import나 export {}가 없으면 의도대로 적용되지 않을 수 있습니다.
// ✅ 최소한의 export로 모듈화
export {};
declare global {
interface Window {
analytics: unknown;
}
}참고 링크
2 sources