.d.ts файлы
Декларационные файлы, ambient declarations, ambient modules
📖 Введение
Представьте, что вы едете за границу. Вам нужен паспорт — документ, который описывает, кто вы. Без паспорта вас не пустят на территорию другой страны. Паспорт не является вами — он просто описывает вас: имя, дата рождения, фото.
.d.ts файлы (декларационные файлы) работают так же — они описывают JavaScript-код для TypeScript, не изменяя его. Это как паспорт для JavaScript-библиотеки: TypeScript читает .d.ts файл и знает, какие функции, классы и типы доступны.
Зачем это нужно? Многие библиотеки написаны на JavaScript. TypeScript не знает об их типах. .d.ts файлы решают эту проблему — они добавляют типизацию к существующему коду.
1 Что такое .d.ts файлы — определение и структура
Файл с расширением .d.ts — это только типы. Он не содержит JavaScript-кода. TypeScript читает его, чтобы узнать типы, а затем игнорирует при компиляции.
Простой пример: Допустим, у вас есть JavaScript-библиотека my-lib.js:
// my-lib.js
function greet(name) {
return 'Привет, ' + name + '!';
}
function add(a, b) {
return a + b;
}
module.exports = { greet, add };
TypeScript не знает типов этой библиотеки. Создадим .d.ts файл:
// my-lib.d.ts
export function greet(name: string): string;
export function add(a: number, b: number): number;
Теперь TypeScript знает типы: greet принимает строку и возвращает строку, add принимает два числа и возвращает число.
2 Ambient declarations — declare ключевое слово
В .d.ts файлах используется ключевое слово declare. Оно говорит: «это существует где-то, просто опиши тип»:
// Описание глобальной переменной
declare const PI: number;
// Описание глобальной функции
declare function greet(name: string): string;
// Описание глобального класса
declare class Logger {
constructor(prefix: string);
log(message: string): void;
warn(message: string): void;
error(message: string): void;
}
// Описание глобального интерфейса
declare interface Config {
apiUrl: string;
timeout: number;
retries: number;
}
// Описание глобального типа
declare type Status = 'loading' | 'success' | 'error';
declare не создаёт JavaScript-код. Он просто описывает, что нечто существует. TypeScript использует эту информацию для проверки типов.
Аналогия: declare — это как оглавление в книге. Оно описывает, что есть в книге, но не является самой книгой.
3 Ambient modules — declare module
declare module используется для описания модулей, которые не имеют типов:
// Описание модуля lodash
declare module 'lodash' {
export function map<T, U>(array: T[], fn: (item: T) => U): U[];
export function filter<T>(array: T[], fn: (item: T) => boolean): T[];
export function reduce<T, U>(array: T[], fn: (acc: U, item: T) => U, initial: U): U;
export function debounce<T extends (...args: any[]) => any>(
fn: T,
wait: number
): T;
export function throttle<T extends (...args: any[]) => any>(
fn: T,
wait: number
): T;
}
// Описание модуля moment
declare module 'moment' {
function moment(input?: string | number | Date): Moment;
interface Moment {
format(format?: string): string;
add(amount: number, unit: string): Moment;
subtract(amount: number, unit: string): Moment;
diff(other: Moment, unit?: string): number;
isValid(): boolean;
toDate(): Date;
}
export default moment;
}
Теперь вы можете импортировать lodash и moment с полной типизацией:
import _ from 'lodash';
import moment from 'moment';
const numbers = [1, 2, 3, 4, 5];
const doubled = _.map(numbers, (n) => n * 2); // number[]
const now = moment();
const tomorrow = now.add(1, 'day').format('YYYY-MM-DD'); // string
4 Где размещать .d.ts файлы — типы и расположение
Есть несколько способов разместить .d.ts файлы:
1. Рядом с .js файлом:
src/
├── utils/
│ ├── math.js // JavaScript-код
│ └── math.d.ts // Типы для math.js
├── services/
│ ├── api.js
│ └── api.d.ts
2. В папке types/ проекта:
src/
├── types/
│ ├── global.d.ts // Глобальные типы
│ ├── env.d.ts // Переменные окружения
│ └── vendor.d.ts // Типы для сторонних библиотек
├── utils/
│ └── math.ts
3. В node_modules/@types:
node_modules/
├── @types/
│ ├── lodash/
│ │ └── index.d.ts // Типы из DefinitelyTyped
│ └── express/
│ └── index.d.ts
Настройка в tsconfig.json:
{
"compilerOptions": {
"typeRoots": ["./src/types", "./node_modules/@types"],
"types": ["node", "jest", "lodash"]
}
}
typeRoots — папки, где искать типы. types — конкретные пакеты типов, которые нужно подключить.
5 Автоматическая генерация .d.ts — declaration: true
TypeScript может автоматически генерировать .d.ts файлы из TypeScript-кода. Это полезно, когда вы пишете библиотеку на TypeScript и хотите предоставить типы пользователям:
// tsconfig.json
{
"compilerOptions": {
"declaration": true,
"declarationDir": "./dist/types"
}
}
Теперь при компиляции TypeScript создаст .d.ts файлы в папке dist/types:
// src/utils.ts
export function add(a: number, b: number): number {
return a + b;
}
export interface User {
id: number;
name: string;
}
export class UserService {
private users: User[] = [];
addUser(name: string): User {
const user = { id: this.users.length + 1, name };
this.users.push(user);
return user;
}
}
// После компиляции в dist/types/utils.d.ts:
export declare function add(a: number, b: number): number;
export interface User {
id: number;
name: string;
}
export declare class UserService {
private users: User[];
addUser(name: string): User;
}
Обратите внимание: в .d.ts файле функция не имеет тела — только сигнатура. Это логично: .d.ts файл описывает что есть, а не как это работает.
🛠 Практический пример: типизация окружения
.d.ts файлы часто используются для типизации переменных окружения:
// env.d.ts
declare namespace NodeJS {
interface ProcessEnv {
NODE_ENV: 'development' | 'production' | 'test';
API_URL: string;
DATABASE_URL: string;
JWT_SECRET: string;
PORT: string;
}
}
// Теперь TypeScript знает о переменных окружения:
const apiUrl = process.env.API_URL; // string
const port = parseInt(process.env.PORT || '3000'); // number
Или для типизации кастомных событий:
// events.d.ts
interface CustomEventMap {
'user:login': { userId: number; timestamp: Date };
'user:logout': { userId: number };
'cart:add': { productId: number; quantity: number };
'cart:remove': { productId: number };
}
declare global {
interface Window {
addEventListener<K extends keyof CustomEventMap>(
type: K,
handler: (event: CustomEvent<CustomEventMap[K]>) => void
): void;
removeEventListener<K extends keyof CustomEventMap>(
type: K,
handler: (event: CustomEvent<CustomEventMap[K]>) => void
): void;
dispatchEvent<K extends keyof CustomEventMap>(
event: CustomEvent<CustomEventMap[K]>
): void;
}
}
6 Глобальные типы — описание глобальных объектов
.d.ts файлы часто используются для описания глобальных объектов, которые доступны без импорта:
// globals.d.ts
declare const API_URL: string;
declare const APP_VERSION: string;
// Описание глобальных функций
declare function trackEvent(category: string, action: string, label?: string): void;
declare function formatMoney(amount: number): string;
Теперь TypeScript знает о глобальных переменных и функциях:
// TypeScript знает типы:
console.log(API_URL); // string
trackEvent('user', 'click', 'button'); // ✅
// Ошибки типов ловятся:
trackEvent(123, 'click'); // ❌ Ошибка типа
7 Типы для webpack и Vite — кастомные модули
.d.ts файлы используются для типизации кастомных модулей в webpack и Vite:
// declarations.d.ts
declare module '*.svg' {
const content: React.FunctionComponent<React.SVGAttributes<SVGElement>>;
export default content;
}
declare module '*.png' {
const content: string;
export default content;
}
declare module '*.css' {
const content: Record<string, string>;
export default content;
}
declare module '*.module.css' {
const classes: Record<string, string>;
export default classes;
}
declare module '*.json' {
const content: any;
export default content;
}
Теперь можно импортировать файлы с правильными типами:
import logo from './logo.svg'; // React-компонент
import image from './image.png'; // строка (URL)
import styles from './styles.module.css'; // объект с классами
import config from './config.json'; // объект
✅ Итого
- .d.ts файлы — декларационные файлы, которые описывают типы JavaScript-кода
- declare — ключевое слово для описания существующих переменных, функций, классов
- declare module — описание модулей, которые не имеют типов
- Типы размещаются в: рядом с .js файлом, в папке types/, в node_modules/@types
- declaration: true в tsconfig.json — автоматическая генерация .d.ts из TypeScript-кода
- .d.ts файлы не содержат JavaScript-кода — только типы и сигнатуры
8 Namespace в .d.ts файлах — описание пространств имён
.d.ts файлы могут содержать namespace для описания глобальных объектов:
// jquery.d.ts
declare namespace jQuery {
function ready(callback: () => void): void;
function ajax(settings: any): void;
interface JQuery {
text(): string;
text(value: string): JQuery;
html(): string;
html(value: string): JQuery;
val(): string;
val(value: string): JQuery;
addClass(className: string): JQuery;
removeClass(className: string): JQuery;
on(event: string, handler: (event: Event) => void): JQuery;
}
function $(selector: string): JQuery;
}
Теперь можно использовать jQuery с полной типизацией:
jQuery.ready(() => {
const element = jQuery.$('.my-class');
const text: string = element.text(); // ✅ TypeScript знает тип
element.on('click', (event) => {
console.log(event.target);
});
});
14 .d.ts и React Hook — типизация хуков
.d.ts файлы могут описывать React-хуки:
// hooks.d.ts
declare module 'my-hooks' {
import { DependencyList } from 'react';
export function useDebounce<T>(value: T, delay: number): T;
export function useThrottle<T>(value: T, limit: number): T;
export function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T) => void];
export function useFetch<T>(url: string): {
data: T | null;
loading: boolean;
error: Error | null;
};
export function useClickOutside(
ref: React.RefObject<HTMLElement>,
handler: () => void
): void;
}
Теперь можно использовать эти хуки с правильными типами:
import { useDebounce, useLocalStorage } from 'my-hooks';
function SearchInput() {
const [query, setQuery] = useLocalStorage<string>('search', '');
const debouncedQuery = useDebounce(query, 300);
return (
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Поиск..."
/>
);
}
15 .d.ts и async/await — описание асинхронных функций
.d.ts файлы могут описывать асинхронные функции с Promise:
// async-utils.d.ts
declare module 'async-utils' {
export function delay(ms: number): Promise<void>;
export function retry<T>(
fn: () => Promise<T>,
attempts: number,
delayMs?: number
): Promise<T>;
export function timeout<T>(
promise: Promise<T>,
ms: number
): Promise<T>;
export function parallel<T>(
promises: Promise<T>[]
): Promise<T[]>;
export function series<T>(
tasks: (() => Promise<T>)[]
): Promise<T[]>;
}
Теперь можно использовать эти функции с async/await:
import { delay, retry, timeout } from 'async-utils';
async function fetchData(): Promise<string> {
await delay(1000);
return 'Данные загружены';
}
// Использование с retry
const result = await retry(fetchData, 3, 1000);
// Использование с timeout
const data = await timeout(fetchData(), 5000);
16 .d.ts и middleware — описание промежуточного ПО
.d.ts файлы могут описывать middleware-функции:
// middleware.d.ts
declare module 'middleware' {
import { Request, Response, NextFunction } from 'express';
export interface Middleware {
(req: Request, res: Response, next: NextFunction): void;
}
export function authenticate: Middleware;
export function validate<T>(schema: {
validate: (data: unknown) => { value: T; error?: Error };
}): Middleware;
export function rateLimit(options: {
windowMs: number;
max: number;
}): Middleware;
export function cors(options?: {
origin?: string | string[];
methods?: string[];
}): Middleware;
}
Теперь можно использовать middleware с правильными типами:
import { authenticate, rateLimit, cors } from 'middleware';
app.use(cors({ origin: 'https://example.com' }));
app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));
app.use('/api', authenticate);
9 .d.ts для сторонних библиотек — когда нет типов
Если у библиотеки нет ни встроенных типов, ни @types пакета, вы можете создать свой .d.ts файл:
// types/old-library.d.ts
declare module 'old-library' {
export function doSomething(input: string): string;
export function doAnotherThing(input: number, options?: {
debug?: boolean;
timeout?: number;
}): Promise<any>;
export interface Config {
apiUrl: string;
retries: number;
}
export class OldClass {
constructor(config: Config);
method(): void;
}
}
Теперь TypeScript знает типы этой библиотеки, и вы можете использовать автодополнение и проверку типов.
10 .d.ts и глобальные библиотеки — подключение через script
Некоторые библиотеки подключаются через <script> и становятся глобальными. Для них нужны .d.ts файлы с глобальными объявлениями:
// library.d.ts
declare global {
interface Window {
myLibrary: {
init(config: object): void;
getData(): Promise<any>;
destroy(): void;
};
}
}
export {};
Теперь TypeScript знает о глобальном объекте window.myLibrary.
11 Практика: создание .d.ts длясобственной библиотеки
Давайте создадим .d.ts файл для небольшой библиотеки утилит:
// my-utils.d.ts
declare module 'my-utils' {
export function formatCurrency(amount: number, currency?: string): string;
export function formatDate(date: Date | string, format?: string): string;
export function debounce<T extends (...args: any[]) => any>(
fn: T,
delay: number
): T;
export function throttle<T extends (...args: any[]) => any>(
fn: T,
limit: number
): T;
export interface Config {
apiUrl: string;
timeout: number;
retries: number;
}
export class Logger {
constructor(prefix: string);
log(message: string): void;
warn(message: string): void;
error(message: string): void;
}
}
Теперь можно использовать эту библиотеку с полной типизацией:
import { formatCurrency, Logger } from 'my-utils';
const formatted = formatCurrency(1500, 'RUB'); // ✅ string
const logger = new Logger('App'); // ✅ Logger
logger.log('Запущено'); // ✅
12 .d.ts иgenerics — описание generic-типов
.d.ts файлы могут описыватьgenerics (generic) типы:
// generic-utils.d.ts
declare module 'generic-utils' {
export function identity<T>(arg: T): T;
export function first<T>(array: T[]): T | undefined;
export function last<T>(array: T[]): T | undefined;
export function flatten<T>(array: T[][]): T[];
export interface Repository<T> {
findById(id: number): Promise<T | null>;
findAll(): Promise<T[]>;
create(item: Omit<T, 'id'>): Promise<T>;
update(id: number, item: Partial<T>): Promise<T>;
delete(id: number): Promise<void>;
}
}
Теперь можно использоватьgenerics с полной типизацией:
import { identity, Repository } from 'generic-utils';
//generics функции
const result = identity<string>('hello'); // string
const first = first<number>([1, 2, 3]); // number | undefined
//generics интерфейс
interface User {
id: number;
name: string;
}
const userRepo: Repository<User> = {
findById: async (id) => ({ id, name: 'Алексей' }),
findAll: async () => [],
create: async (user) => ({ id: 1, ...user }),
update: async (id, user) => ({ id, name: user.name || '' }),
delete: async (id) => {}
};
13 .d.ts и аннотации — описание функций
.d.ts файлы могут описывать функции с различными типами аргументов:
// helpers.d.ts
declare module 'helpers' {
// Обязательные аргументы
export function add(a: number, b: number): number;
// Опциональные аргументы
export function greet(name: string, greeting?: string): string;
// Аргументы по умолчанию
export function createUser(name: string, role?: 'admin' | 'user'): object;
// Rest-параметры
export function sum(...numbers: number[]): number;
// Функция с callback
export function fetchData(
url: string,
callback: (error: Error | null, data?: any) => void
): void;
// Функция с перегрузкой
export function parse(input: string): string;
export function parse(input: number): number;
export function parse(input: string | number): string | number;
}
Теперь можно использовать эти функции с правильными типами:
import { add, greet, sum, fetchData, parse } from 'helpers';
const result = add(1, 2); // ✅ number
const message = greet('Мир'); // ✅ string
const total = sum(1, 2, 3, 4, 5); // ✅ number
fetchData('/api/data', (error, data) => {
if (error) {
console.error(error); // ✅ Error
} else {
console.log(data); // ✅ any
}
});
const str = parse('hello'); // ✅ string
const num = parse(42); // ✅ number
Проверяем понимание
5 вопросов