Avatar ·

Как импортировать класс в файл объявления типов (.d.ts) в TypeScript?

Всем привет! Пишу на TypeScript недавно и столкнулся с проблемой. У меня есть библиотека, написанная на JavaScript, и я хочу добавить к ней файл объявления типов (.d.ts), чтобы TypeScript понимал типы. В библиотеке есть класс `User` и несколько вспомогательных функций, которые этот класс используют. Например, функция `createUser(name)` возвращает новый экземпляр `User`. Я создал файл `my-library.d.ts` и хочу в нём описать типы. Вот пример моего кода: ```typescript // my-library.d.ts class User { name: string; constructor(name: string); greet(): string; } export function createUser(name: string): User; ``` Но когда я пытаюсь использовать `User` внутри другого файла `.d.ts` (например, `extra-types.d.ts`), мне нужно импортировать класс `User`, чтобы ссылаться на него. Проблема в том, что в файлах `.d.ts` нельзя писать `import { User } from './my-library'` — компилятор ругается, что импорт не разрешён в файле объявления типов, если я не использую `export` или `import` как тип. Я пробовал использовать `import type { User } from './my-library'`, но всё равно получаю ошибки. Как правильно импортировать класс или интерфейс из одного `.d.ts` файла в другой? Нужно ли менять структуру файлов? Заранее спасибо за помощь! <<>>

Решение: используйте import type или глобальное объявление

Проблема в том, что файлы .d.ts имеют строгие правила: если вы используете import или export в корне файла, он становится модулем. В модуле все типы и значения локальны, и чтобы ссылаться на типы из другого файла, нужно их явно импортировать. Но обычный import (даже с типом) может конфликтовать, если вы не хотите делать файл модулем.

Вот два основных рабочих подхода:

1. Используйте import type (рекомендуется для модулей)

Если ваш файл .d.ts уже содержит export или import (то есть он модульный), то используйте import type. Это правильный способ импортировать только типы, без рантайм-значений. Пример:

// my-library.d.ts
export class User {
  name: string;
  constructor(name: string);
  greet(): string;
}

export function createUser(name: string): User;
// extra-types.d.ts
import type { User } from './my-library';

export function getUserInfo(user: User): string;

Обратите внимание: import type стирается при компиляции — в JavaScript его не будет. Это идеально для файлов объявлений.

2. Используйте глобальные типы (без импорта/экспорта)

Если вы не хотите делать файл модулем (то есть все объявления должны быть глобально доступны без импорта), объявите типы глобально. Для этого:

  • В файле my-library.d.ts не используйте export — просто объявите класс на уровне модуля.
  • В файле extra-types.d.ts просто используйте User напрямую.
// my-library.d.ts
declare class User {
  name: string;
  constructor(name: string);
  greet(): string;
}

declare function createUser(name: string): User;
// extra-types.d.ts
// Не нужно импортировать — User доступен глобально
declare function getUserInfo(user: User): string;

Но будьте осторожны: глобальные типы могут конфликтовать с другими библиотеками. Лучше использовать модульный подход.

3. Используйте extends или interface (альтернатива)

Если вам нужно только описать структуру, а не сам класс, можно объявить интерфейс в одном файле и импортировать его:

// my-library.d.ts
export interface IUser {
  name: string;
  greet(): string;
}

export class User implements IUser {
  name: string;
  constructor(name: string);
  greet(): string;
}
// extra-types.d.ts
import type { IUser } from './my-library';

export function getUserInfo(user: IUser): string;

Почему import { User } не работает?

Потому что User в .d.ts — это класс (значение и тип одновременно). Если вы пишете import { User }, TypeScript ожидает, что это рантайм-импорт, но в .d.ts это запрещено. import type решает эту проблему, сообщая компилятору: «мне нужен только тип, не генерируй код».

Итог: если ваш файл .d.ts — модуль (содержит export), используйте import type. Если это глобальный файл — просто объявляйте без export.

<<>>

Дополнительный подход: объединение в один файл

Если у вас небольшой проект, проще всего объединить все объявления в один файл .d.ts. Это избавит от проблем с импортами полностью. Например:

// types.d.ts
declare class User {
  name: string;
  constructor(name: string);
  greet(): string;
}

declare function createUser(name: string): User;

declare function getUserInfo(user: User): string;

Такой файл будет глобальным, и все типы доступны без импорта. Недостаток — файл может стать слишком большим, но для небольших библиотек это отличное решение.

Используйте declare module

Если вы типизируете внешнюю библиотеку (например, из npm), можно использовать declare module с относительным импортом:

// extra-types.d.ts
declare module 'my-library' {
  export class User {
    name: string;
    constructor(name: string);
    greet(): string;
  }
}

// Теперь можно импортировать User из 'my-library' в других .d.ts файлах
import { User } from 'my-library';

Этот подход часто используется в DefinitelyTyped (@types/*). Он позволяет переопределить или расширить типы для существующей библиотеки.

Выберите способ, который лучше подходит под архитектуру вашего проекта. Для простых случаев — глобальный файл, для сложных — import type или declare module.