본문으로 건너뛰기
Advertisement

8.2 선언 파일(.d.ts) — 타입 정의의 세계

선언 파일이란?

.d.ts 파일은 타입 정보만 담은 파일입니다. 실제 구현 코드는 없고, TypeScript 컴파일러에게 "이 값의 타입은 이렇다"고 알려주는 역할을 합니다.

// math.d.ts — 선언 파일 (구현 없음)
export declare function add(a: number, b: number): number;
export declare function subtract(a: number, b: number): number;
export declare const PI: number;
// math.js — 실제 구현 (타입 없음)
export function add(a, b) { return a + b; }
export function subtract(a, b) { return a - b; }
export const PI = 3.14159;

TypeScript는 .js + .d.ts 쌍을 보고 타입 안전하게 JavaScript 라이브러리를 사용할 수 있습니다.


.d.ts 파일이 필요한 이유

상황설명
JS 라이브러리 사용lodash, jquery 등은 JS로 작성됨
빌드 결과물 타입 제공라이브러리 배포 시 .js + .d.ts 함께 제공
성능 최적화skipLibCheck와 함께 타입 체크 속도 향상

tsc로 .d.ts 자동 생성

// tsconfig.json
{
"compilerOptions": {
"declaration": true, // .d.ts 파일 생성
"declarationMap": true, // .d.ts.map 소스맵 생성
"declarationDir": "./types", // .d.ts 출력 디렉토리 (선택)
"emitDeclarationOnly": true // .d.ts만 생성 (번들러가 JS 처리 시)
}
}
// src/utils.ts
export function greet(name: string): string {
return `Hello, ${name}!`;
}

export interface Config {
timeout: number;
retries: number;
}

컴파일 결과:

// dist/utils.d.ts (자동 생성)
export declare function greet(name: string): string;

export interface Config {
timeout: number;
retries: number;
}

@types/ 패키지 — DefinitelyTyped

JavaScript 라이브러리 중 TypeScript 타입 정의가 내장되지 않은 경우, @types/ 패키지를 별도로 설치합니다.

npm install --save-dev @types/node      # Node.js 타입
npm install --save-dev @types/express # Express 타입
npm install --save-dev @types/lodash # lodash 타입
npm install --save-dev @types/jest # Jest 타입

설치 여부 확인 방법

import express from 'express';
// 빨간 밑줄이 뜨면 @types/express 설치 필요
// 또는 라이브러리에 자체 타입 정의 내장됨 (예: axios)

타입 내장 라이브러리 vs @types 필요 라이브러리

// 타입 내장 (별도 설치 불필요)
import axios from 'axios'; // axios: package.json에 "types" 필드 포함
import { z } from 'zod'; // zod: TypeScript로 작성됨
import { PrismaClient } from '@prisma/client';

// @types 필요
import express from 'express'; // @types/express 필요
import _ from 'lodash'; // @types/lodash 필요
import fs from 'fs'; // @types/node에 포함

선언 파일 직접 작성하기

함수 선언

// globals.d.ts
declare function fetchUser(id: number): Promise<User>;
declare function formatDate(date: Date, format: string): string;

변수/상수 선언

declare const API_URL: string;
declare let currentUser: User | null;
declare var __DEV__: boolean;

인터페이스와 타입 선언

// types.d.ts
interface User {
id: number;
name: string;
email: string;
role: 'admin' | 'user';
}

type Nullable<T> = T | null;
type Optional<T> = T | undefined;

클래스 선언

declare class EventEmitter {
on(event: string, listener: (...args: any[]) => void): this;
off(event: string, listener: (...args: any[]) => void): this;
emit(event: string, ...args: any[]): boolean;
}

모듈 선언 파일 작성

기존 JavaScript 라이브러리에 타입을 추가할 때 사용합니다.

// legacy-lib.d.ts
declare module 'legacy-lib' {
export function initialize(config: LegacyConfig): void;
export function process(data: string): string;

interface LegacyConfig {
apiKey: string;
timeout?: number;
debug?: boolean;
}
}
// 사용 시
import { initialize, process } from 'legacy-lib';
initialize({ apiKey: 'abc123' }); // ✅ 타입 안전

실전 예제: 라이브러리에 .d.ts 제공하기

my-utils/
├── src/
│ ├── string.ts
│ └── array.ts
├── dist/
│ ├── string.js
│ ├── string.d.ts
│ ├── array.js
│ └── array.d.ts
├── package.json
└── tsconfig.json
// package.json
{
"name": "my-utils",
"version": "1.0.0",
"main": "./dist/index.js",
"types": "./dist/index.d.ts", // 타입 진입점
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts" // ESM 타입 진입점
}
}
}
// src/string.ts
/**
* 문자열의 첫 글자를 대문자로 변환
*/
export function capitalize(str: string): string {
if (!str) return str;
return str.charAt(0).toUpperCase() + str.slice(1);
}

/**
* 문자열을 camelCase로 변환
*/
export function toCamelCase(str: string): string {
return str
.split(/[-_\s]+/)
.map((word, i) => i === 0 ? word.toLowerCase() : capitalize(word))
.join('');
}

컴파일 후:

// dist/string.d.ts (자동 생성)
/**
* 문자열의 첫 글자를 대문자로 변환
*/
export declare function capitalize(str: string): string;
/**
* 문자열을 camelCase로 변환
*/
export declare function toCamelCase(str: string): string;

JSDoc이 .d.ts에도 보존됩니다. IDE에서 마우스를 올리면 문서가 표시됩니다.


DefinitelyTyped에 기여하기

DefinitelyTyped는 커뮤니티가 관리하는 TypeScript 타입 정의 저장소입니다.

기여 절차

  1. 저장소 fork
git clone https://github.com/DefinitelyTyped/DefinitelyTyped
cd DefinitelyTyped
  1. 타입 파일 작성
types/
└── my-library/
├── index.d.ts # 타입 정의
├── tsconfig.json # 타입 체크 설정
└── my-library-tests.ts # 타입 테스트
  1. index.d.ts 작성 규칙
// types/my-library/index.d.ts

// 헤더 주석 (필수)
// Type definitions for my-library 1.0
// Project: https://github.com/author/my-library
// Definitions by: Your Name <https://github.com/yourname>

export interface Options {
timeout?: number;
retries?: number;
}

export function request(url: string, options?: Options): Promise<Response>;
  1. 타입 테스트 작성
// my-library-tests.ts
import { request, Options } from 'my-library';

// $ExpectType 주석으로 타입 검증
const opts: Options = { timeout: 5000 };
const result = request('https://api.example.com', opts);
// $ExpectType Promise<Response>
  1. PR 제출 → 리뷰 후 @types/my-library 패키지로 배포

고수 팁

1. typeRoots로 타입 탐색 경로 지정

{
"compilerOptions": {
"typeRoots": [
"./node_modules/@types", // 기본값
"./types" // 커스텀 타입 디렉토리
]
}
}

2. types로 사용할 @types 패키지 제한

{
"compilerOptions": {
"types": ["node", "jest"] // 이 두 개만 전역 타입으로 포함
}
}

3. 인라인 타입 선언 오버라이드

// src/types/express/index.d.ts
import 'express';

declare module 'express' {
interface Request {
user?: AuthUser; // express의 Request 타입 확장
}
}

4. skipLibCheck: true로 성능 최적화

{
"compilerOptions": {
"skipLibCheck": true // .d.ts 파일 타입 체크 건너뜀 (빌드 속도↑)
}
}
Advertisement