Объявление Ambient с импортированным типом в TypeScript

У меня есть файл декларации в моем проекте типа TypeScript:

// myapp.d.ts
declare namespace MyApp {
  interface MyThing {
    prop1: string
    prop2: number
  }
}

Это отлично работает, и я могу использовать это пространство имен в любом месте моего проекта, не импортируя его.

Теперь мне нужно импортировать тип из стороннего модуля и использовать его в своей эмбиентной декларации:

// myapp.d.ts
import {SomeType} from 'module'

declare namespace MyApp {
  interface MyThing {
    prop1: string
    prop2: number
    prop3: SomeType
  }
}

Компилятор теперь жалуется, что он не может найти пространство имен "MyApp", предположительно потому, что импорт не позволяет ему быть эмбиентом.

Есть ли простой способ сохранить эмбиентность декларации при использовании сторонних типов?

Ответ 1

Да, есть способ. Это станет легче в TypeScript 2.9 с помощью import() типа, но это также возможно в более ранних версиях TypeScript.

Следующий файл представляет собой скрипт. Вещи, объявленные в скрипте, добавляются в глобальную область. Вот почему вы можете использовать MyApp.MyThing без его импорта.

// myapp.d.ts
declare namespace MyApp {
  interface MyThing {
    prop1: string;
    prop2: number;
  }
}

Скрипты ограничены в том, что они не могут ничего импортировать; когда вы добавляете import, скрипт становится модулем. Я думаю, что это странно, если не сказать больше, но это то, что есть. Важно то, что вещи, определенные в модуле, относятся к этому модулю и не добавляются в глобальную область.

Однако модули также могут добавлять объявления в глобальную область видимости, помещая их в global:

// myapp.d.ts
import {SomeType} from 'module';

declare global {
  namespace MyApp {
    interface MyThing {
      prop1: string;
      prop2: number;
      prop3: SomeType;
    }
  }
}

Этот файл является модулем, но он добавляет объявление MyApp.MyThing в глобальную область, так что вы все равно можете использовать MyApp.MyThing в другом коде TypeScript, не импортируя его.

Обратите внимание, что использование расширения .d.ts не имеет ничего общего с возможностью доступа к интерфейсу без его импорта. Оба вышеуказанных файла могли быть файлами .ts и вести себя точно так же.

Ответ 2

К сожалению нет. Как вы уже выяснили, это работает только с внутренним кодом, например без внешних зависимостей. Вы должны либо экспортировать свое пространство имен, либо пойти на экспорт классов и использовать модули ES6. Но и то, и другое приведет к необходимости import ваши вещи. Что-то, чего вы пытаетесь избежать, как я полагаю.

Лично я считаю, что более комфортно использовать импорт (даже внутренние) во всем коде. Это по той простой причине, что при открытии определенного файла (класса) все его зависимости сразу видны.

Подробный пример уже был рассмотрен в вопросе "Как использовать пространства имён с импортом в TypeScript".

Примечание для других: "пространство имен, доступное внутренне", также является причиной того, что я не рассматриваю этот дублирующий вопрос.

Ответ 3

Начиная с TS 2.9 это возможно с помощью import():

// myapp.d.ts
declare type SomeType = import('module').SomeType;
declare type SomeDefaultType = import('module-with-default-export').default;


declare namespace MyApp {
  interface MyThing {
    prop1: string;
    prop2: number;
    prop3: SomeType | SomeDefaultType;
  }
}