Ch 02. 선언 파일(.d.ts) 이해하기
.d.ts 파일은 TypeScript에게 타입 정보를 전달하는 파일입니다. 실행 코드는 없고 타입 선언만 있습니다. JavaScript 라이브러리의 타입을 제공하거나, 환경에 존재하는 전역 변수를 TypeScript에 알릴 때 사용합니다.
.d.ts 파일의 구조
.d.ts 파일은 일반 TypeScript 파일과 비슷하지만 모든 선언 앞에 declare 키워드가 붙거나, 구현이 없는 타입 선언만 존재합니다.
// 파일: src/types/api.d.ts// 인터페이스와 타입 별칭은 declare 없이도 됩니다export interface User { id: number; name: string; email: string;}export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";// 함수는 구현 없이 서명만export declare function fetchUser(id: number): Promise<User>;// 클래스는 구현 없이 멤버만export declare class ApiClient { constructor(baseUrl: string, apiKey: string); get<T>(path: string): Promise<T>; post<T>(path: string, body: unknown): Promise<T>;}
declare 키워드와 앰비언트 선언
declare는 "이 값은 어딘가에 존재한다"고 컴파일러에 알리는 키워드입니다. 구현 코드 없이 타입만 선언합니다.
// 파일: src/types/globals.d.ts// 전역 변수 선언 — 런타임에 window.APP_VERSION이 존재한다고 알림declare const APP_VERSION: string;declare const APP_ENV: "development" | "staging" | "production";// 전역 함수 선언declare function log(message: string, level?: "info" | "warn" | "error"): void;// 전역 클래스 선언declare class EventEmitter { on(event: string, listener: (...args: unknown[]) => void): this; off(event: string, listener: (...args: unknown[]) => void): this; emit(event: string, ...args: unknown[]): boolean;}
// 파일: src/app.ts// globals.d.ts가 있으면 이렇게 사용 가능console.log(APP_VERSION); // 오류 없음log("앱 시작", "info"); // 오류 없음
모듈 선언
.d.ts 파일에서 모듈을 선언하면 TypeScript 타입이 없는 모듈에 타입을 제공할 수 있습니다.
// 파일: src/types/my-library.d.tsdeclare module "my-library" { export interface Options { timeout: number; retries: number; } export function connect(url: string, options?: Options): Promise<void>; export function disconnect(): void; export class Client { constructor(url: string); send(data: string): Promise<void>; close(): void; } export default Client;}
전역 타입 확장
기존 타입에 새 속성을 추가하거나 기존 인터페이스를 확장할 때 선언 병합(declaration merging)을 사용합니다.
// 파일: src/types/express.d.ts// Express의 Request 타입에 user 속성 추가declare namespace Express { interface Request { user?: { id: number; name: string; role: string; }; sessionId?: string; }}
// 파일: src/types/window.d.ts// window 객체에 커스텀 속성 추가interface Window { analytics: { track(event: string, properties?: Record<string, unknown>): void; identify(userId: string): void; }; __APP_CONFIG__: { apiUrl: string; featureFlags: Record<string, boolean>; };}
환경별 타입 선언
빌드 환경에 따라 다른 타입이 필요할 때 사용합니다.
// 파일: src/types/env.d.ts// Vite의 import.meta.env에 커스텀 환경변수 추가interface ImportMetaEnv { readonly VITE_API_URL: string; readonly VITE_APP_TITLE: string; readonly VITE_AUTH_TOKEN: string;}interface ImportMeta { readonly env: ImportMetaEnv;}
// 파일: src/api/client.ts// env.d.ts 덕분에 타입 안전하게 환경변수 접근const apiUrl = import.meta.env.VITE_API_URL; // string으로 추론됨
.d.ts 파일 자동 생성
TypeScript 컴파일러로 소스 코드에서 .d.ts 파일을 자동으로 생성할 수 있습니다. 라이브러리를 배포할 때 사용합니다.
// tsconfig.json{ "compilerOptions": { "declaration": true, // .d.ts 생성 "declarationDir": "./dist", // 저장 위치 "declarationMap": true, // .d.ts.map 생성 (소스맵) "emitDeclarationOnly": false // .js와 함께 생성 }}
// 파일: src/math.ts (소스)export function add(a: number, b: number): number { return a + b;}export function multiply(a: number, b: number): number { return a * b;}
// 자동 생성된 dist/math.d.tsexport declare function add(a: number, b: number): number;export declare function multiply(a: number, b: number): number;
.d.ts 파일이 참조되는 방법
TypeScript는 세 가지 방법으로 .d.ts 파일을 찾습니다.
첫 번째는 tsconfig.json의 include나 files 설정입니다. 두 번째는 /// 트리플 슬래시 참조입니다.
// 파일: src/app.ts/// <reference types="node" /> // @types/node 참조/// <reference path="../types/global.d.ts" /> // 특정 파일 참조// 이후 코드에서 참조된 타입 사용 가능process.env.NODE_ENV; // @types/node의 타입
세 번째는 패키지의 types 또는 typings 필드입니다.
// package.json{ "name": "my-library", "main": "./dist/index.js", "types": "./dist/index.d.ts"}
선언 파일은 TypeScript 생태계의 접착제 역할을 합니다. JavaScript 라이브러리를 TypeScript에서 안전하게 사용하고, 런타임 환경에서 제공하는 전역 API에 타입을 부여하고, 프로젝트 전반에 걸쳐 공유되는 타입 정의를 관리하는 핵심 메커니즘입니다.