DefinitelyTyped具有许多库的类型定义,但是当Javascript实现与Typescript分开时,例如当库通过a分配给窗口的属性时,常常我找不到一种使用它们的好方法>
<script src="https://example.com/library.js">
标签,当我管理的JS包位于另一个单独的脚本中时。 (即使将包括库的所有内容都捆绑在一起是标准且可靠的方法,但出于问题的考虑,我不能选择将库导入到我的TS项目中。)例如,说我为名为myLib
的库找到了一个漂亮的定义文件:
// my-lib.d.ts
export const doThing1: () => number;
export const doThing2: () => string;
export const version: string;
export interface AnInterface {
foo: string;
}
export as namespace myLib;
在JS中,我可以通过调用window.myLib.doThing1()
和window.myLib.doThing2()
来使用myLib。如何导入整个window.myLib
对象的形状,以便可以将其声明为window
的属性?我看到我可以导入导出的接口,例如:
// index.ts
import { AnInterface } from './my-lib';
const something: AnInterface = { foo: 'foo' };
console.log(something.foo);
这可行,但是我想访问实际库对象的形状及其属性值(函数和字符串等),而不仅仅是接口。如果我愿意
import * as myLib from './my-lib';
然后myLib
标识符成为一个命名空间,我可以从中引用导出的接口,但是就像上面一样,我仍然无法从{访问export const
和export function
形状{1}}。 (而且,当然,尝试使用导入的名称空间声明库对象是行不通的:my-lib.d.ts
即使这样做,也不一定安全,因为为浏览器打包的库可能会的结构与库的Node导出对象略有不同)
如果我手动将Cannot use namespace 'myLib' as a type.
的一部分复制并粘贴到自己的脚本中,则可以一起破解一些有效的东西:
d.ts
但这是一个混乱,耗时的过程,并且肯定不是进行此类操作的正确方法。当我遇到这种情况时,我会喜欢做一些简短而优雅的事情,例如:
// index.ts
declare global {
interface Window {
myLib: {
doThing1: () => number;
doThing2: () => string;
version: string;
};
}
}
某些定义文件包括库对象的接口,例如jQuery,它的作用是:
// index.ts
import myLibObjectInterface from './my-lib.d.ts'; // this line is not correct
declare global {
interface Window {
myLib: myLibObjectInterface
}
}
然后一切都很好-我只能使用// index.d.ts
/// <reference path="JQuery.d.ts" />
// jQuery.d.ts
interface JQuery<TElement = HTMLElement> extends Iterable<TElement> {
// lots and lots of definitions
,但是许多最初不是为浏览器使用而创建的库没有提供这样的接口。
如前所述,最好的解决方案是将库的实现与TS项目集成在一起,允许interface Window { $: jQuery }
都使用库及其类型,而不必大惊小怪,但这是不可能的。 ,我还有什么 good 选项吗?我可以检查真实库对象上的属性,并向包含所有此类属性及其类型的定义文件添加接口,但必须修改DT接受并被其他人使用的半规范源定义文件错误。我希望能够导入定义文件导出的形状,并在不修改原始文件的情况下从它们创建一个接口,但这可能是不可能的。
是否有更优雅的解决方案,或者碰巧遇到的定义文件根本不适合我的目标,因此必须对其进行修改?
答案 0 :(得分:8)
如果模块具有export as namespace myLib
,则模块已经将库导出为全局对象。因此,您可以将库用作:
let a:myLib.AnInterface;
let b = myLib.doThing1();
只要您正在使用该库的文件本身不是模块本身(即,它不包含import
,也不包含export
语句),这就是事实。
export {} // module now
let a:myLib.AnInterface; // Types are still ok without the import
let b = myLib.doThing1(); // Expressions are not ok, ERR: 'myLib' refers to a UMD global, but the current file is a module. Consider adding an import instead.ts(2686)
您可以使用导入类型(相信2.9中添加了),将与图书馆类型相同的属性添加到Window
// myLibGlobal.d.ts
// must not be a module, must not contain import/ export
interface Window {
myLib: typeof import('./myLib') // lib name here
}
//usage.ts
export {} // module
let a:myLib.AnInterface; // Types are still ok without the import (if we have the export as namespace
let b = window.myLib.doThing1(); // acces through window ok now
修改
显然,Typescript团队实际上已经在为这个问题进行某些工作。如您在此PR中所读,打字稿的下一个版本将包含一个allowUmdGlobalAccess
标志。此标志将允许从模块访问UMD模块全局变量。将此标志设置为true时,此代码将有效:
export {} // module now
let a:myLib.AnInterface; // Types are still ok without the import
let b = myLib.doThing1(); // ok, on typescript@3.5.0-dev.20190425
这意味着您无需使用窗口就可以访问模块导出。如果全局导出与我期望的浏览器兼容,那么它将起作用。
答案 1 :(得分:3)
您在做什么
当库将自身分配给窗口的属性时
这称为 UMD包。这些是通过在文档的<script />
标签内添加链接而消耗的,它们将自身附加到全局范围。
不必以这种方式使用UMD软件包-使用import
(或require
)语句也可以将它们作为模块使用。
TypeScript支持这两种用法。
如何键入UMD软件包
declare namespace Foo {
export const bar: string;
export type MeaningOfLife = number;
}
export as namespace Foo;
export = Foo;
此定义告诉TypeScript:
bar
的变量bar
,也可以使用通配符(*
)导入来导入整个名称空间。脚本和模块有什么区别?
脚本将是HTML文档中<script />
标记内运行的一段JavaScript。可以内联它或从文件中加载它。这就是浏览器始终使用JavaScript的方式。
模块是一个JavaScript(TypeScript)文件,具有至少一个import
或export
语句。它们是ECMAScript标准的一部分,并非到处都受支持。通常,您在项目中创建 modules ,然后让诸如Webpack的捆绑器为您的应用程序创建要使用的捆绑器。
使用UMD软件包
通过访问全局Foo
命名空间来使用脚本,变量和类型(就像jQuery和$
一样):
const bar = Foo.bar;
const meaningOfLife: Foo.MeaningOfLife = 42;
使用导入类型语法导入bar
类型的脚本:
const baz: typeof import ('foo').bar = 'hello';
一个模块bar
变量是使用命名的import导入的。
import { bar } from 'foo';
bar.toUpperCase();
一个模块,整个包作为名称空间导入:
import * as foo from 'foo';
foo.bar.toUpperCase();
全局范围与窗口
如果正确键入了此类库,则作为使用者的您无需执行任何操作即可使其正常工作。它会自动在您的全局范围内可用,不需要为Window
进行扩充。
但是,如果您想将库的内容显式地附加到window
上,也可以这样做:
declare global {
interface Window {
Foo: typeof import('foo');
}
}
window.Foo.bar;