简体   繁体   English

在 TypeScript 中使用导入类型的环境声明

[英]Ambient declaration with an imported type in TypeScript

I have a declaration file in my TypeScript project like so:我的 TypeScript 项目中有一个声明文件,如下所示:

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

This works great and I can use this namespace anywhere in my project without having to import it.这很好用,我可以在我的项目中的任何地方使用这个命名空间,而无需导入它。

I now need to import a type from a 3rd party module and use it in my ambient declaration:我现在需要从 3rd 方模块导入一个类型并在我的环境声明中使用它:

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

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

The compiler now complains that it can't find namespace 'MyApp', presumably because the import prevents it from being ambient.编译器现在抱怨它找不到命名空间“MyApp”,大概是因为导入阻止它成为环境。

Is there some easy way to retain the ambient-ness of the declaration whilst utilising 3rd-party types?是否有一些简单的方法可以在使用 3rd-party 类型的同时保留声明的环境性?

Yes, there is a way.是的,有办法。 It has become easier in TypeScript 2.9 by using import() a type but it's also possible in earlier versions of TypeScript.在 TypeScript 2.9 中使用import() 类型变得更容易,但在早期版本的 TypeScript 中也可以。

The following file is a script .以下文件是一个脚本 Things declared in a script are added to the global scope .脚本中声明的东西被添加到全局范围中。 This is why you can use MyApp.MyThing without importing it.这就是您无需导入即可使用MyApp.MyThing的原因。

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

Scripts are limited in that they cannot import anything;脚本的局限性在于它们不能导入任何东西; when you add an import , the script becomes a module .当你添加一个import时,脚本就变成了一个模块 I think it's weird to say the least, but it is what it is.我认为至少可以说很奇怪,但事实就是如此。 What matters is that things defined in a module are scoped to that module, and are not added to the global scope.重要的是,模块中定义的内容仅限于该模块,而不是添加到全局范围。

However, modules can add declarations to the global scope too, by putting them inside global :但是,模块也可以将声明添加到全局范围,方法是将它们放在global中:

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

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

This file is a module , but it adds a declaration of MyApp.MyThing to the global scope, so you can still use MyApp.MyThing in other TypeScript code without importing it.该文件是一个模块,但它在全局范围内添加了MyApp.MyThing的声明,因此您仍然可以在其他 TypeScript 代码中使用MyApp.MyThing而无需导入它。

Note that using the .d.ts extension has nothing to do with you being able to access the interface without importing it.请注意,使用.d.ts扩展名与您无需导入即可访问界面无关。 Both of the above files could have been .ts files and would still behave exactly the same.上述两个文件都可能是.ts文件,并且行为仍然完全相同。

Since TS 2.9 this is possible with import() :从 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;
  }
}

Don't know if you're still looking for an answer, but this is the correct way to handle it and still be able to eg define generic modules, not just named namespaces: (original answer this is based on)不知道您是否仍在寻找答案,但这是处理它的正确方法,并且仍然能够例如定义通用模块,而不仅仅是命名空间:( 基于原始答案)

// myapp.d.ts

declare namespace MyApp {
  import {SomeType} from 'module'
  interface MyThing {
    prop1: string
    prop2: number
    prop3: SomeType
  }
}

Unfortunately, no.抱歉不行。 As you already figured out this only works with internal code, eg without external dependencies.正如您已经发现的那样,这只适用于内部代码,例如没有外部依赖项。 You should either go with exporting your namespace, or go for export of classes and use ES6 modules.您应该导出命名空间,或者导出类并使用 ES6 模块。 But both will result in you requiring to import your stuff.但两者都会导致你需要import你的东西。 Something that you're trying to avoid, as I believe.正如我所相信的那样,您试图避免的事情。

Personally, I find it more comforting to actually use imports (even internals) throughout the code.就个人而言,我发现在整个代码中实际使用导入(甚至是内部)更令人欣慰。 This for the simple reason that when opening a specific file (class), all its dependencies are immediately visible.原因很简单,当打开特定文件(类)时,它的所有依赖项都立即可见。

A thorough example was already addressed in the question "How to use namespaces with import in TypeScript" . “如何在 TypeScript 中使用带有导入的命名空间”这个问题已经解决了一个完整的示例。

Note for others: the "namespace being available internally" is also the reason why I'm not considering this a duplicate question.其他人请注意:“内部可用的命名空间”也是我不认为这是一个重复问题的原因。

In my case, I have a global ambient variable created by electron for the API to interact with the node process.就我而言,我有一个由电子创建的全局环境变量,用于 API 与节点进程交互。 I needed to define an interface with a property type that referenced a class from a third-party dependency rather than a type/interface.我需要定义一个接口,其属性类型从第三方依赖项而不是类型/接口中引用一个

The TS 2.9 import() syntax worked for me when using a type/interface, but not a class. TS 2.9 import() 语法在使用类型/接口而不是类时对我有用。 Specifically I am trying to use rsjx.Observable in my interface.具体来说,我正在尝试在我的界面中使用 rsjx.Observable 。 I was able to find a way to handle this that I haven't seen mentioned anywhere.我能够找到一种我在任何地方都没有提到过的方法来处理这个问题。 I hope it can help someone else.我希望它可以帮助别人。

You can combine module + ambient by making a module with types you want to export, then use the TS 2.9 import() syntax to pull that into an ambient declaration.您可以通过使用要导出的类型创建模块来组合模块 + 环境,然后使用 TS 2.9 import() 语法将其拉入环境声明中。

// types/electronAPI.ts
import { IpcRendererEvent } from 'electron'
import { Observable } from 'rxjs'

export type ThingStatus = 'on' | 'off'

export interface MyElectronAPI {
  thingObservable: Observable<[IpcRendererEvent, ThingStatus]>
}
// types/global.d.ts
declare type MyElectronAPI = import('./electronAPI').MyElectronAPI

const electronAPI: MyElectronAPI
// someFile.tsx

// I get all my types :)
elecronAPI.thingObservable.subscribe(([_e, status]) => { ... } )

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM