Написание declaration files
Написание declaration files
Declaration Reference
Прежде чем писать .d.ts, нужно определить, какие конструкции языка использовать. Вот основные паттерны:
Объявление переменных и констант
// Глобальная переменная
declare var DEBUG: boolean;
// Глобальная константа (нельзя переприсвоить)
declare const VERSION: string;
// Глобальная переменная, которую можно только читать
declare let currentUser: string;
Объявление функций
// Простая функция
declare function greet(name: string): void;
// Функция с перегрузками
declare function createElement(tag: "div"): HTMLDivElement;
declare function createElement(tag: "span"): HTMLSpanElement;
declare function createElement(tag: string): HTMLElement;
// Функция, принимающая callback
declare function onReady(callback: () => void): void;
Объявление объектов с методами
// Объект-пространство имён с вложенными методами
declare namespace myLib {
function makeGreeting(s: string): string;
let numberOfGreetings: number;
}
Объявление классов
declare class Greeter {
constructor(greeting: string);
greeting: string;
showGreeting(): void;
}
Объявление интерфейсов и типов
// Интерфейс (можно расширять)
declare interface GreetingSettings {
greeting: string;
duration?: number;
color?: string;
}
// Псевдоним типа
declare type GreetingStyle = "formal" | "casual" | "silent";
Шаблоны .d.ts
Шаблон: module-function
Для библиотек, экспортирующих одну функцию как default:
// string-utils.d.ts
// Использование: import capitalize from "string-utils";
// capitalize("hello") → "Hello"
export default function capitalize(input: string): string;
// Дополнительные именованные экспорты
export function truncate(input: string, maxLength: number): string;
export function camelize(input: string): string;
Шаблон: module-class
Для библиотек, экспортирующих класс:
// super-logger.d.ts
// Использование: import Logger from "super-logger";
// const log = new Logger("app");
declare class Logger {
constructor(prefix: string);
info(message: string): void;
warn(message: string): void;
error(message: string, error?: Error): void;
setLevel(level: "debug" | "info" | "warn" | "error"): void;
}
export default Logger;
// Дополнительные экспорты
export type LogLevel = "debug" | "info" | "warn" | "error";
export interface LogEntry {
level: LogLevel;
message: string;
timestamp: Date;
}
Шаблон: module-plugin
Для библиотек, расширяющих другие библиотеки:
// express-jwt.d.ts
// Плагин, добавляющий свойство user к Request в Express
import { Request } from "express";
// Расширяем существующий интерфейс
declare module "express" {
interface Request {
user?: {
id: string;
roles: string[];
};
}
}
// Собственный экспорт плагина
export interface JwtOptions {
secret: string;
algorithms: string[];
}
export default function expressJwt(options: JwtOptions): RequestHandler;
Шаблон: global-modifying-module
Для модулей, которые при импорте модифицируют глобальную область:
// observable-array.d.ts
// При импорте добавляет метод observe ко всем массивам
export {};
declare global {
interface Array<T> {
observe(callback: (changes: ArrayChange<T>[]) => void): void;
unobserve(): void;
}
interface ArrayChange<T> {
type: "add" | "remove" | "update";
index: number;
value: T;
oldValue?: T;
}
}
Шаблон: global
Для чисто глобальных библиотек (без модульной системы):
// analytics.d.ts
// Использование: analytics.track("click", { button: "signup" });
declare namespace analytics {
function init(apiKey: string): void;
function track(event: string, properties?: Record<string, unknown>): void;
function identify(userId: string, traits?: Record<string, unknown>): void;
function page(name?: string): void;
interface Config {
apiKey: string;
debug?: boolean;
flushInterval?: number;
}
}
Do’s and Don’ts
Типы в callback-ах
// DO: используй конкретный тип возврата void для callback
declare function onEvent(callback: () => void): void;
// DON'T: не используй any для callback
// declare function onEvent(callback: () => any): void;
Перегрузки
// DO: сортируй перегрузки от конкретных к общим
declare function parse(input: "json"): JsonResult;
declare function parse(input: "xml"): XmlResult;
declare function parse(input: string): Result;
// DON'T: не ставь общую перегрузку первой
// declare function parse(input: string): Result;
// declare function parse(input: "json"): JsonResult; // Никогда не вызовется!
TypeScript выбирает первую подходящую перегрузку. Если общая сигнатура стоит первой, конкретные никогда не сработают.
Необязательные параметры
// DO: используй необязательные параметры
declare function render(template: string, data?: object): string;
// DON'T: не создавай перегрузки только ради опциональности
// declare function render(template: string): string;
// declare function render(template: string, data: object): string;
Union vs перегрузки
// DO: используй union, когда тип параметра не влияет на тип возврата
declare function format(value: string | number): string;
// DO: используй перегрузки, когда тип параметра определяет тип возврата
declare function parse(input: string): StringResult;
declare function parse(input: Buffer): BufferResult;
Не используй any без необходимости
// DON'T: any уничтожает типобезопасность
declare function process(data: any): any;
// DO: используй unknown или дженерик
declare function process(data: unknown): unknown;
declare function process<T>(data: T): ProcessedResult<T>;
Не используй Object, String, Number, Boolean
// DON'T: объектные обёртки -- не то, что нужно
declare function lower(s: String): String;
// DO: используй примитивные типы
declare function lower(s: string): string;
Организация файлов деклараций
Один файл для всей библиотеки
Для небольших библиотек – один файл index.d.ts:
// types/small-lib/index.d.ts
export function doSomething(input: string): number;
export function doSomethingElse(input: number): string;
export interface Options {
verbose?: boolean;
}
Несколько файлов с реэкспортом
Для крупных библиотек – разделение по модулям:
// types/big-lib/index.d.ts
export * from "./core";
export * from "./utils";
export * from "./types";
// types/big-lib/core.d.ts
export declare class BigLib {
constructor(options: import("./types").Options);
run(): void;
}
// types/big-lib/types.d.ts
export interface Options {
debug?: boolean;
timeout?: number;
}
Тестирование деклараций
Проверить корректность .d.ts можно с помощью тестовых файлов:
// tests/types-test.ts
import { doSomething, Options } from "my-lib";
// Проверяем, что типы работают
const result: number = doSomething("test");
// Проверяем, что невалидный код даёт ошибку
// @ts-expect-error -- аргумент должен быть строкой
doSomething(123);
// @ts-expect-error -- результат -- число, не строка
const wrong: string = doSomething("test");
Директива @ts-expect-error гарантирует, что следующая строка действительно содержит ошибку типов. Если ошибки нет – TypeScript выдаст предупреждение.
Итоги
- Выбирайте шаблон
.d.tsв зависимости от типа библиотеки: module-function, module-class, module-plugin, global, global-modifying-module - Сортируйте перегрузки от конкретных к общим – TypeScript берёт первую подходящую
- Используйте
unknownвместоany, конкретные типы вместо обёрток (string, неString) - Предпочитайте optional-параметры вместо перегрузок для необязательных аргументов
- Тестируйте декларации с помощью
@ts-expect-error