typescript构建库-TypeScript 使用命名空间为外部库提供类型限定

TypeScript 如何使用命名空间(超详细)本文详细介绍了 TypeScript 命名空间的使用,并看看使用命名空间时生成的 JavaScript 代码

在这里,我们将介绍命名空间有用的场景之一:为外部库创建模块声明。 为此,我们将在 TypeScript 项目中编写一个新文件来声明类型,然后修改 tsconfig.json 文件以使 TypeScript 编译器识别这些类型。

%> 注意:要执行后续步骤,您需要一个能够访问文件系统的 TypeScript 环境。 如果您使用的是 TypeScript Playground,则可以通过单击底部菜单中的“导入”,然后单击“在 CodeSandbox 中打开”,将现有代码导入到 CodeSandbox 项目中。 这将允许我们创建新文件并编辑 tsconfig.json 文件。

并非 npm 注册表中可用的每个包都捆绑了自己的 TypeScript 模块声明。 这意味着在项目中安装包时,您可能会遇到与包缺少类型声明相关的编译错误,或者必须使用所有类型设置为 any 的库。 根据您对 TypeScript 的严格程度,这可能是一个不受欢迎的结果。

希望这个包将有一个由 DefinetelyTyped 社区创建的 @types 包,允许安装该包并获取该库的工作类型。 然而,情况并非总是如此,有时我们必须处理一个不绑定自己的类型模块声明的库。 在这些情况下,如果您想保持代码完全类型安全,则必须自己创建模块声明。

例如,假设您正在使用名为 example-vector3 的矢量库,该库使用单个方法 add 导出单个类 Vector3。 此模式用于将两个 Vector3 向量相乘。

库中的代码可能类似于以下内容:

export class Vector3 {
  super(x, y, z) {
    this.x = x;
    this.y = y;
    this.z = z;
  }
  add(vec) {
    let x = this.x + vector.x;
    let y = this.y + vector.y;
    let z = this.z + vector.z;
    let newVector = new Vector3(x, y, z);
    return newVector
  }
}

这会导入一个类,该类创建带有表示向量坐标分量的 x、y 和 z 属性的向量。

接下来,看一下使用假定库的示例代码:

索引.ts

import { Vector3 } from "example-vector3";
const v1 = new Vector3(1, 2, 3);
const v2 = new Vector3(1, 2, 3);
const v3 = v1.add(v2);

example-vector3 库未与其自己的类型声明绑定,因此 TypeScript 编译器将给出错误 2307:

Cannot find module 'example-vector3' or its corresponding type declarations. ts(2307)

为了解决这个问题,我们现在将为这个包创建一个类型声明文件。 首先,创建一个名为 types/example-vector3/index.d.ts 的新文件typescript构建库,并在您喜欢的编辑器中打开它。 在此文件中写入以下代码:

类型/example-vector3/index.d.ts

declare module "example-vector3" {
  export = vector3;
  namespace vector3 {
  }
}

在此代码中,我们为 example-vector3 模块创建类型声明。 代码的第一部分是声明模块块本身。 TypeScript 编译器将解析该块并解释其中的所有内容,就好像它是模块本身的类型表示一样。 这意味着无论我们在这里声明什么,TypeScript 都会用来推断模块的类型。 现在,假设该模块导入了一个名为 vector3 的命名空间,该命名空间当前为空。

保存并退出该文件。

TypeScript 编译器当前不知道我们的声明文件,因此它必须包含在 tsconfig.json 中。 为此,请通过将 types 属性添加到 compilerOptions 选项来编辑项目 tsconfig.json:

tsconfig.json

{
  "compilerOptions": {
    ...
    "types": ["./types/example-vector3/index.d.ts"]
  }
}

现在,如果我们回到原来的代码,我们会看到错误已经改变。 TypeScript 编译器现在给出错误 2305:

Module '"example-vector3"' has no exported member 'Vector3'. ts(2305)

在为 example-vector3 创建模块声明时,导出当前设置为空命名空间。 Vector3 类不是从此命名空间导入的。

重新打开 types/example-vector3/index.d.ts 并编译以下代码:

类型/example-vector3/index.d.ts

declare module "example-vector3" {
  export = vector3;
  namespace vector3 {
    export class Vector3 {
      constructor(x: number, y: number, z: number);
      add(vec: Vector3): Vector3;
    }
  }
}

在此代码中typescript构建库,请注意现在如何将类导入到 vector3 命名空间中。 模块声明的主要目标是为库公开的值提供类型信息。 这样,我们就可以以类型安全的方式使用它。

在这些情况下,我们知道 example-vector3 库提供了一个名为 Vector3 的类,该类在构造函数中接受三个数字,并具有一个 add 方法,该方法将两个 Vector3 实例相乘并返回一个新实例作为结果。 这里不需要提供实现,只需提供类型信息本身。 不提供实现的声明在 TypeScript 中称为环境声明,通常在 .d.ts 文件中创建。

此代码现在将正确编译并具有 Vector3 类的正确类型。

使用命名空间,您可以将库导入隔离到单个类型单元中,在本例中为 vector3 命名空间。 这使得声明自定义模块变得更加容易,甚至可以通过将类型声明提交到 DefinetelyTyped 存储库来向所有开发人员提供类型声明。