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 属性的向量,用于表示向量的坐标分量。

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

index.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 的新文件,然后在自己喜欢的编辑器中打开它。 在此文件中写入以下代码:

types/example-vector3/index.d.ts

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

  namespace vector3 {
  }
}

 

在此代码中,我们正在为 example-vector3 模块创建类型声明。 代码的第一部分是声明 module 块本身。 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 并编写以下代码:

types/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;
    }
  }
}

 

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

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

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

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

posted @ 2022-02-11 14:40  迹忆客  阅读(208)  评论(0编辑  收藏  举报