本页内容
类型和类型声明
Deno 的设计原则之一是,没有非标准模块解析。当 TypeScript 对文件进行类型检查时,它只关心该文件的类型,并且 tsc
编译器有大量的逻辑来尝试解析这些类型。默认情况下,它期望模棱两可的模块说明符带有扩展名,并将尝试在 .ts
说明符下查找文件,然后是 .d.ts
,最后是 .js
(以及当模块解析设置为 "node"
时的一整套其他逻辑)。Deno 处理显式说明符。
但这可能会导致一些问题。例如,假设我想使用一个已经编译成 JavaScript 的 TypeScript 文件以及一个类型定义文件。所以我有一个 mod.js
和一个 mod.d.ts
。如果我尝试将 mod.js
导入到 Deno 中,它只会做我要求它做的事情,并导入 mod.js
,但这意味着我的代码不会像 TypeScript 考虑 mod.d.ts
文件来代替 mod.js
文件那样得到很好的类型检查。
为了在 Deno 中支持这一点,Deno 有两种解决方案,其中有一个解决方案的变体可以增强支持。你遇到的两种主要情况是
- 作为 JavaScript 模块的导入者,我知道应该将哪些类型应用于该模块。
- 作为 JavaScript 模块的提供者,我知道应该将哪些类型应用于该模块。
后一种情况是更好的情况,这意味着你作为模块的提供者或托管者,每个人都可以使用它,而无需弄清楚如何为 JavaScript 模块解析类型,但是当你使用你可能无法直接控制的模块时,也需要能够执行前者。
导入时提供类型 跳转到标题
如果你正在使用一个 JavaScript 模块,并且你已经创建了类型(一个 .d.ts
文件)或者以其他方式获得了你想要使用的类型,你可以指示 Deno 在类型检查时使用该文件而不是 JavaScript 文件,使用 @deno-types
编译器提示。@deno-types
必须是单行双斜线注释,当使用时会影响下一个导入或重新导出语句。
例如,如果我有一个 JavaScript 模块 coolLib.js
,并且我有一个单独的 coolLib.d.ts
文件想要使用,我会像这样导入它
// @deno-types="./coolLib.d.ts"
import * as coolLib from "./coolLib.js";
在类型检查 coolLib
和你在文件中的使用方式时,将使用 coolLib.d.ts
类型,而不是查看 JavaScript 文件。
编译器提示的模式匹配比较宽松,它会接受说明符的带引号和不带引号的值,以及等号前后接受空格。
托管时提供类型 跳转到标题
如果你控制着模块的源代码,或者你控制着文件在 Web 服务器上的托管方式,那么有两种方法可以告知 Deno 给定模块的类型,而无需要求导入者执行任何特殊操作。
使用三斜线引用指令 跳转到标题
Deno 支持使用三斜线引用 types
指令,它采用 TypeScript 文件中 TypeScript 使用的引用注释来包含其他文件,并将其仅应用于 JavaScript 文件。
例如,如果我创建了coolLib.js
,并且在它旁边创建了我的库类型定义coolLib.d.ts
,我可以在coolLib.js
文件中执行以下操作
/// <reference types="./coolLib.d.ts" />
// ... the rest of the JavaScript ...
当 Deno 遇到此指令时,它将解析./coolLib.d.ts
文件,并在 TypeScript 对文件进行类型检查时使用它,而不是 JavaScript 文件,但在运行程序时仍然加载 JavaScript 文件。
ℹ️ 注意这是一个针对 TypeScript 的重新利用的指令,它只适用于 JavaScript 文件。在 TypeScript 文件中使用
types
的三斜杠引用指令在 Deno 中也能正常工作,但与path
指令的行为基本相同。
使用 X-TypeScript-Types 头部 跳转到标题
与三斜杠指令类似,Deno 支持一个用于远程模块的头部,该头部指示 Deno 在哪里找到给定模块的类型。例如,https://example.com/coolLib.js
的响应可能如下所示
HTTP/1.1 200 OK
Content-Type: application/javascript; charset=UTF-8
Content-Length: 648
X-TypeScript-Types: ./coolLib.d.ts
当看到此头部时,Deno 将尝试检索https://example.com/coolLib.d.ts
并在类型检查原始模块时使用它。
使用环境或全局类型 跳转到标题
总的来说,最好在 Deno 中使用模块/UMD 类型定义,其中模块明确导入它依赖的类型。模块化类型定义可以通过类型定义中的declare global
来表达对全局范围的增强。例如
declare global {
var AGlobalString: string;
}
这将使AGlobalString
在导入类型定义时在全局命名空间中可用。
但在某些情况下,当利用其他现有的类型库时,可能无法利用模块化类型定义。因此,有一些方法可以在类型检查程序时包含任意类型定义。
使用三斜杠指令 跳转到标题
此选项将类型定义与代码本身耦合。通过在模块类型附近添加三斜杠types
指令,对文件进行类型检查将包含类型定义。例如
/// <reference types="./types.d.ts" />
提供的说明符与 Deno 中的任何其他说明符一样解析,这意味着它需要扩展名,并且相对于引用它的模块。它也可以是完全限定的 URL
/// <reference types="https://deno.land/x/pkg@1.0.0/types.d.ts" />
使用配置文件 跳转到标题
另一种选择是使用一个配置了包含类型定义的配置文件,方法是为"compilerOptions"
提供一个"types"
值。例如
{
"compilerOptions": {
"types": [
"./types.d.ts",
"https://deno.land/x/pkg@1.0.0/types.d.ts",
"/Users/me/pkg/types.d.ts"
]
}
}
与上面的三斜杠引用一样,在"types"
数组中提供的说明符将像 Deno 中的其他说明符一样解析。对于相对说明符,它将相对于配置文件的路径解析。确保通过指定--config=path/to/file
标志告诉 Deno 使用此文件。
类型检查 Web Workers 跳转到标题
当 Deno 在 Web Worker 中加载 TypeScript 模块时,它将自动根据 Deno Web Worker 库对模块及其依赖项进行类型检查。这在其他上下文中(如deno cache
或编辑器中)可能是一个挑战。有几种方法可以指示 Deno 使用 Worker 库而不是标准 Deno 库。
使用三斜杠指令 跳转到标题
此选项将库设置与代码本身耦合。通过在 Worker 脚本入口点的顶部附近添加以下三斜杠指令,Deno 现在将将其类型检查为 Deno Worker 脚本,而与模块的分析方式无关
/// <reference no-default-lib="true" />
/// <reference lib="deno.worker" />
第一个指令确保不使用其他默认库。如果省略此指令,您将获得一些冲突的类型定义,因为 Deno 将尝试同时应用标准 Deno 库。第二个指令指示 Deno 应用内置的 Deno Worker 类型定义以及依赖库(如"esnext"
)。
当您运行deno cache
或deno bundle
命令或使用使用 Deno 语言服务器的 IDE 时,Deno 应该自动检测这些指令并在类型检查时应用正确的库。
这样做的一个缺点是,它使代码在其他非 Deno 平台(如tsc
)上难以移植,因为只有 Deno 将"deno.worker"
库内置其中。
使用配置文件 跳转到标题
另一个选择是使用一个配置好的配置文件来应用库文件。一个最小的可行文件看起来像这样
{
"compilerOptions": {
"target": "esnext",
"lib": ["deno.worker"]
}
}
然后在命令行上运行命令时,你需要传递--config path/to/file
参数,或者如果你使用的是利用Deno语言服务器的IDE,则设置deno.config
设置。
如果你还有非工作脚本,你需要省略--config
参数,或者配置一个满足非工作脚本需求的参数。
重要事项 跳转到标题
类型声明语义 跳转到标题
类型声明文件 (.d.ts
文件) 遵循与 Deno 中其他文件相同的语义。这意味着声明文件被假定为模块声明 (UMD 声明) 而不是环境/全局声明。Deno 如何处理环境/全局声明是不可预测的。
此外,如果类型声明导入其他内容,例如另一个 .d.ts
文件,则其解析遵循 Deno 的正常导入规则。对于许多在网上生成和可用的 .d.ts
文件,它们可能与 Deno 不兼容。
esm.sh 是一个 CDN,默认情况下提供类型声明 (通过 X-TypeScript-Types
标头)。可以通过在导入 URL 后追加 ?no-dts
来禁用它
import React from "https://esm.sh/react?no-dts";
JavaScript 在类型检查时的行为 跳转到标题
如果你在 Deno 中将 JavaScript 导入到 TypeScript 中,并且没有类型,即使你将 checkJs
设置为 false
(Deno 的默认值),TypeScript 编译器仍然会访问 JavaScript 模块并尝试对其进行一些静态分析,至少尝试确定该模块的导出形状以验证 TypeScript 文件中的导入。
当尝试导入 "常规" ES 模块时,这通常不会出现问题,但在某些情况下,如果模块具有特殊的打包方式,或者是一个全局 UMD 模块,TypeScript 对模块的分析可能会失败并导致误导性的错误。在这种情况下,最好的做法是使用上述方法之一提供某种形式的类型。
内部机制 跳转到标题
虽然不需要了解 Deno 的内部工作原理才能很好地利用 TypeScript 与 Deno,但了解其工作原理会有所帮助。
在执行或编译任何代码之前,Deno 通过解析根模块生成一个模块图,然后检测其所有依赖项,然后递归地检索和解析这些模块,直到所有依赖项都被检索到。
对于每个依赖项,有两个潜在的 "插槽" 被使用。一个是代码插槽,另一个是类型插槽。当模块图被填充时,如果模块是或可以被发射到 JavaScript,它将填充代码插槽,而只有类型依赖项,例如 .d.ts
文件,将填充类型插槽。
当模块图被构建,并且需要对图进行类型检查时,Deno 启动 TypeScript 编译器并向其提供需要作为 JavaScript 潜在发射的模块的名称。在此过程中,TypeScript 编译器将请求额外的模块,Deno 将查看依赖项的插槽,在提供代码插槽之前,如果类型插槽已填充,则提供类型插槽。
这意味着当你导入 .d.ts
模块,或者你使用上述解决方案之一为 JavaScript 代码提供替代类型模块时,在解析模块时,提供给 TypeScript 的是这些内容。