8.5 Ambient 선언 — declare로 타입만 선언하기
Ambient 선언이란?
**Ambient 선언(주변 선언)**은 declare 키워드를 사용해 구현 없이 타입 정보만 제공하는 방법입니다. 이미 어딘가에 존재하는 값(런타임, 외부 라이브러리, 번들러 주입 등)에 대해 TypeScript에게 타입을 알려줍니다.
// ✅ Ambient 선언: 구현 없이 타입만
declare const API_URL: string;
declare function fetch(url: string): Promise<Response>;
declare class EventEmitter { }
// ❌ 일반 선언: 구현 포함
const API_URL = 'https://api.example.com';
function fetch(url: string) { /* 구현 */ }
class EventEmitter { /* 구현 */ }
declare 키워드의 종류
declare const / let / var
// 전역 변수 선언
declare const VERSION: string;
declare let currentUser: User | null;
declare var __ENV__: 'development' | 'production' | 'test';
// 사용
console.log(VERSION); // ✅ 타입: string
declare function
// 전역 함수 선언
declare function require(id: string): any;
declare function parseInt(s: string, radix?: number): number;
declare function setTimeout(callback: () => void, ms: number): number;
declare class
// 전역 클래스 선언
declare class MySDK {
constructor(config: SDKConfig);
initialize(): Promise<void>;
track(event: string, data?: Record<string, unknown>): void;
destroy(): void;
}
declare enum
// 컴파일 후 사라지는 enum 선언
declare enum Direction {
Up,
Down,
Left,
Right,
}
declare type / interface
// 타입과 인터페이스는 declare 없이도 사용 가능하지만 일관성을 위해 사용
declare type Callback = (err: Error | null, result?: unknown) => void;
declare interface EventHandler {
(event: MouseEvent): void;
capture?: boolean;
}
declare module
외부 모듈 전체 타입 선언
JavaScript 라이브러리에 타입이 없을 때 사용합니다.
// src/types/legacy-analytics.d.ts
declare module 'legacy-analytics' {
export interface TrackEvent {
name: string;
properties?: Record<string, string | number | boolean>;
userId?: string;
}
export function initialize(apiKey: string): void;
export function track(event: TrackEvent): void;
export function identify(userId: string, traits?: Record<string, unknown>): void;
export function page(name?: string): void;
export default {
initialize,
track,
identify,
page,
};
}
빠른 타입 선언 (any로 일단 통과)
긴박한 상황에서 오류를 빠르게 해소하는 방법:
// src/types/quick-fix.d.ts
declare module 'untyped-lib'; // 모든 export가 any
declare module 'another-untyped-lib' {
const lib: any;
export default lib;
}
비JS 파일 모듈 선언
webpack, Vite 등에서 이미지, CSS, SVG 등을 import할 때 사용합니다.
// src/types/assets.d.ts
// 이미지 파일
declare module '*.png' {
const src: string;
export default src;
}
declare module '*.jpg' {
const src: string;
export default src;
}
declare module '*.svg' {
import React from 'react';
export const ReactComponent: React.FunctionComponent<
React.SVGProps<SVGSVGElement> & { title?: string }
>;
const src: string;
export default src;
}
// CSS Modules
declare module '*.module.css' {
const styles: Record<string, string>;
export default styles;
}
declare module '*.module.scss' {
const styles: Record<string, string>;
export default styles;
}
// JSON 파일 (tsconfig에 resolveJsonModule이 없을 때)
declare module '*.json' {
const value: unknown;
export default value;
}
// 텍스트 파일
declare module '*.txt' {
const content: string;
export default content;
}
// WASM
declare module '*.wasm' {
const buffer: ArrayBuffer;
export default buffer;
}
declare global
모듈 파일(import/export 포함) 안에서 전역 타입을 선언할 때 사용합니다.
// src/types/global.d.ts
import type { Logger } from '../utils/logger';
// 전역 선언은 declare global 블록 안에
declare global {
// 전역 변수
const __DEV__: boolean;
const __VERSION__: string;
// window 확장
interface Window {
logger: Logger;
analytics: Analytics;
}
// Node.js 전역 타입 확장
namespace NodeJS {
interface ProcessEnv {
NODE_ENV: 'development' | 'production' | 'test';
DATABASE_URL: string;
JWT_SECRET: string;
PORT?: string;
}
}
}
export {}; // 이 파일을 모듈로 만들기 위한 빈 export
declare namespace
네임스페이스를 사용한 타입 그룹화입니다. 특히 전역 라이브러리(<script> 태그로 로드)에 타입을 붙일 때 유용합니다.
// jQuery 스타일 전역 라이브러리 타입 선언
declare namespace $ {
function ajax(url: string, settings?: AjaxSettings): JQueryXHR;
function get(url: string): JQueryXHR;
interface AjaxSettings {
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
data?: Record<string, unknown>;
success?: (data: unknown) => void;
error?: (error: Error) => void;
}
interface JQueryXHR {
done(callback: (data: unknown) => void): this;
fail(callback: (error: Error) => void): this;
always(callback: () => void): this;
}
}
// 전역 함수도 함께
declare function $(selector: string): JQuery;
declare function $(callback: () => void): JQuery;
중첩 네임스페이스
declare namespace MyApp {
namespace API {
interface Request {
url: string;
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
headers?: Record<string, string>;
}
interface Response<T = unknown> {
data: T;
status: number;
message: string;
}
}
namespace Auth {
interface User {
id: string;
email: string;
role: 'admin' | 'user';
}
interface Session {
user: User;
token: string;
expiresAt: Date;
}
}
}
// 사용
const req: MyApp.API.Request = {
url: '/users',
method: 'GET',
};
const session: MyApp.Auth.Session = { /* ... */ };
실전 프로젝트 타입 파일 구조
src/
└── types/
├── assets.d.ts # 이미지, CSS, SVG 모듈 선언
├── env.d.ts # process.env 타입
├── global.d.ts # 전역 타입 (window, 유틸리티 타입)
├── express.d.ts # Express 모듈 보강
└── vendor.d.ts # 타입 없는 서드파티 라이브러리
env.d.ts 예시 (Vite 프로젝트)
// src/types/env.d.ts
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_URL: string;
readonly VITE_APP_TITLE: string;
readonly VITE_STRIPE_KEY: string;
readonly VITE_GA_ID?: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
// 사용 시
const apiUrl = import.meta.env.VITE_API_URL; // ✅ string
const gaId = import.meta.env.VITE_GA_ID; // ✅ string | undefined
트리플 슬래시 지시자 (Triple-Slash Directives)
파일 맨 위에 특별한 주석으로 타입 참조를 선언합니다.
/// <reference types="node" /> // @types/node 포함
/// <reference lib="es2022" /> // ES2022 lib 포함
/// <reference path="./custom.d.ts" /> // 특정 파일 참조
주로 .d.ts 파일에서 사용합니다.
// my-lib.d.ts
/// <reference types="node" />
declare module 'my-lib' {
import { EventEmitter } from 'events'; // Node.js 타입 사용
class MyLib extends EventEmitter {
constructor(options: MyLibOptions);
start(): Promise<void>;
}
interface MyLibOptions {
port: number;
host?: string;
}
export = MyLib;
}
고수 팁
1. declare module '*' — 와일드카드 모듈 선언
// 알 수 없는 모든 모듈을 any로 처리 (최후의 수단)
declare module '*';
2. Ambient 선언 vs 일반 선언 선택 기준
| 상황 | 선택 |
|---|---|
| 런타임에 이미 존재하는 값 | declare (ambient) |
| TypeScript가 컴파일할 값 | 일반 선언 |
| 외부 JS 라이브러리 타입 | declare module |
| webpack/Vite 처리 파일 | declare module '*.ext' |
3. export = vs export default 차이
// CJS 스타일 라이브러리
declare module 'my-cjs-lib' {
function create(): Instance;
export = create; // module.exports = create
}
// ESM 스타일 라이브러리
declare module 'my-esm-lib' {
function create(): Instance;
export default create; // export default create
}
4. 웹팩 플러그인 타입 선언 패턴
// 번들러가 주입하는 전역 상수
declare const __VERSION__: string;
declare const __BUILD_TIME__: string;
declare const __COMMIT_HASH__: string;