如何在 TypeScript 中使用命名空间

发表于 2年以前  | 总阅读数:436 次

介绍

TypeScript 是 JavaScript 语言的扩展,它使用 JavaScript 运行时和编译时类型检查器。TypeScript 提供了多种方法来表示代码中的对象,其中一种是使用接口。 TypeScript 中的接口有两种使用场景:您可以创建类必须遵循的约定,例如,这些类必须实现的成员,还可以在应用程序中表示类型,就像普通的类型声明一样。

您可能会注意到接口和类型共享一组相似的功能。

事实上,一个几乎总是可以替代另一个。

主要区别在于接口可能对同一个接口有多个声明,TypeScript 将合并这些声明,而类型只能声明一次。您还可以使用类型来创建原始类型(例如字符串和布尔值)的别名,这是接口无法做到的。

TypeScript 中的接口是表示类型结构的强大方法。它们允许您以类型安全的方式使用这些结构并同时记录它们,从而直接改善开发人员体验。

在今天的文章中,我们将在 TypeScript 中创建接口,学习如何使用它们,并了解普通类型和接口之间的区别。

我们将尝试不同的代码示例,可以在 TypeScript 环境或 TypeScript Playground(一个允许您直接在浏览器中编写 TypeScript 的在线环境)中遵循这些示例。

准备工作

要完成今天的示例,我们将需要做如下准备工作:

  • 一个环境。我们可以执行 TypeScript 程序以跟随示例。要在本地计算机上进行设置,我们将需要准备以下内容。

  • 为了运行处理 TypeScript 相关包的开发环境,同时安装了 Node 和 npm(或 yarn)。本文教程中使用 Node.js 版本 为14.3.0 和 npm 版本 6.14.5 进行了测试。要在 macOS 或 Ubuntu 18.04 上安装,请按照如何在 macOS 上安装 Node.js 和创建本地开发环境或如何在 Ubuntu 18.04 上安装 Node.js 的使用 PPA 安装部分中的步骤进行操作。如果您使用的是适用于 Linux 的 Windows 子系统 (WSL),这也适用。

  • 此外,我们需要在机器上安装 TypeScript 编译器 (tsc)。为此,请参阅官方 TypeScript 网站。

  • 如果你不想在本地机器上创建 TypeScript 环境,你可以使用官方的 TypeScript Playground 来跟随。

  • 您将需要足够的 JavaScript 知识,尤其是 ES6+ 语法,例如解构、rest 运算符和导入/导出。如果您需要有关这些主题的更多信息,建议阅读我们的如何用 JavaScript 编写代码系列。

  • 本文教程将参考支持 TypeScript 并显示内联错误的文本编辑器的各个方面。这不是使用 TypeScript 所必需的,但确实可以更多地利用 TypeScript 功能。为了获得这些好处,您可以使用像 Visual Studio Code 这样的文本编辑器,它完全支持开箱即用的 TypeScript。你也可以在 TypeScript Playground 中尝试这些好处。

本教程中显示的所有示例都是使用 TypeScript 4.2.2 版创建的。

在 TypeScript 中创建命名空间

在本节中,我们将一起来学习在 TypeScript 中创建命名空间以说明一般语法。

要创建命名空间,我们将使用命名空间关键字,后跟命名空间的名称,然后是 {} 块。

例如,我们将创建一个 DatabaseEntity 命名空间来保存数据库实体,就像我们使用对象关系映射 (ORM) 库一样。

将以下代码添加到新的 TypeScript 文件中:

namespace DatabaseEntity {
}

这声明了 DatabaseEntity 命名空间,但尚未向该命名空间添加代码。 接下来,在命名空间中添加一个 User 类来表示数据库中的一个 User 实体:

namespace DatabaseEntity {
  class User {
    constructor(public name: string) {}
  }
}

我们可以在命名空间中正常使用 User 类。 为了说明这一点,创建一个新的 User 实例并将其存储在 newUser 变量中:

namespace DatabaseEntity {
  class User {
    constructor(public name: string) {}
  }

  const newUser = new User("Jon");
}

这是有效的代码。

但是,如果我们尝试在命名空间之外使用 User,TypeScript 编译器会给我们返回错误 2339:

Output
Property 'User' does not exist on type 'typeof DatabaseEntity'. (2339)

如果我们想在命名空间之外使用类,则必须首先导出 User 类以在外部可用,如下面突出显示的代码所示:

namespace DatabaseEntity {
  exportclass User {
    constructor(public name: string) {}
  }

  const newUser = new User("Jon");
}

我们现在可以使用完全限定名称访问 DatabaseEntity 命名空间之外的 User 类。 在这种情况下,完全限定名称是 DatabaseEntity.User:


namespace DatabaseEntity {
  export class User {
    constructor(public name: string) {}
  }

  const newUser = new User("Jon");
}

const newUserOutsideNamespace = new DatabaseEntity.User("Jane");

我们可以从命名空间中导出任何内容,包括变量,然后这些变量将成为命名空间中的属性。

在以下代码中,我们将导出 newUser 变量:

namespace DatabaseEntity {
  export class User {
    constructor(public name: string) {}
  }

  exportconst newUser = new User("Jon");
}

console.log(DatabaseEntity.newUser.name);

由于变量 newUser 已导出,因此,我们可以将其作为命名空间的属性进行访问。 运行此代码会将以下内容打印到控制台:

Output
Jon

就像接口一样,TypeScript 中的命名空间也允许声明合并。 这意味着同一命名空间的多个声明将合并为一个声明。 如果我们需要稍后在代码中扩展命名空间,这可以增加命名空间的灵活性。

使用前面的示例,这意味着如果我们再次声明 DatabaseEntity 命名空间,我们将能够使用更多属性扩展命名空间。 使用另一个命名空间声明将一个新类 UserRole 添加到我们的 DatabaseEntity 命名空间:

namespace DatabaseEntity {
  export class User {
    constructor(public name: string) {}
  }

  export const newUser = new User("Jon");
}

namespace DatabaseEntity {
  export class UserRole {
    constructor(public user: User, public role: string) {}
  }

  export const newUserRole = new UserRole(newUser, "admin");
}

在新的 DatabaseEntity 命名空间声明中,我们可以使用以前在 DatabaseEntity 命名空间中导出的任何成员,包括从以前的声明中导出的成员,而不必使用它们的完全限定名。

我们正在使用在第一个命名空间中声明的名称来将 UserRole 构造函数中的用户参数的类型设置为 User 类型,并在使用 newUser 值创建新的 UserRole 实例时。这仅是可能的,因为,我们在之前的命名空间声明中导出了这些内容。

现在,我们已经了解了命名空间的基本语法,我们可以继续研究 TypeScript 编译器如何将命名空间转换为 JavaScript。

检查使用命名空间时生成的 JavaScript 代码

TypeScript 中的命名空间不仅仅是一个编译时特性。他们还更改了生成的 JavaScript 代码。要了解有关命名空间如何工作的更多信息,我们可以分析支持此 TypeScript 功能的 JavaScript。

在这一步中,我们将获取上一节中的代码片段并检查它们的底层 JavaScript 实现。

以我们在第一个示例中使用的代码为例:

namespace DatabaseEntity {
  export class User {
    constructor(public name: string) {}
  }

  export const newUser = new User("Jon");
}

console.log(DatabaseEntity.newUser.name);

TypeScript 编译器会为此 TypeScript 片段生成以下 JavaScript 代码:

"use strict";
var DatabaseEntity;
(function (DatabaseEntity) {
    class User {
        constructor(name) {
            this.name = name;
        }
    }
    DatabaseEntity.User = User;
    DatabaseEntity.newUser = new User("Jon");
})(DatabaseEntity || (DatabaseEntity = {}));
console.log(DatabaseEntity.newUser.name);

为了声明 DatabaseEntity 命名空间,TypeScript 编译器创建一个名为 DatabaseEntity 的未初始化变量,然后,创建一个立即调用函数表达式 (IIFE)。 此 IIFE 接收单个参数 DatabaseEntity || (DatabaseEntity = {}),这是 DatabaseEntity 变量的当前值。 如果未设置为真值,则将变量的值设置为空对象。

在将 DatabaseEntity 的值传递给 IIFE 时将其设置为空值是可行的,因为赋值操作的返回值是被赋值的值。 在这种情况下,这是空对象。

在 IIFE 内部,创建了 User 类,然后,将其分配给 DatabaseEntity 对象的 User 属性。 newUser 属性也是如此,我们将属性分配给新 User 实例的值。

现在看一下第二个代码示例,其中有多个命名空间声明:


namespace DatabaseEntity {
  export class User {
    constructor(public name: string) {}
  }

  export const newUser = new User("Jon");
}

namespace DatabaseEntity {
  export class UserRole {
    constructor(public user: User, public role: string) {}
  }

  export const newUserRole = new UserRole(newUser, "admin");
}

生成的 JavaScript 代码如下所示:


"use strict";
var DatabaseEntity;
(function (DatabaseEntity) {
    class User {
        constructor(name) {
            this.name = name;
        }
    }
    DatabaseEntity.User = User;
    DatabaseEntity.newUser = new User("Jon");
})(DatabaseEntity || (DatabaseEntity = {}));
(function (DatabaseEntity) {
    class UserRole {
        constructor(user, role) {
            this.user = user;
            this.role = role;
        }
    }
    DatabaseEntity.UserRole = UserRole;
    DatabaseEntity.newUserRole = new UserRole(DatabaseEntity.newUser, "admin");
})(DatabaseEntity || (DatabaseEntity = {}));

代码的开头看起来与之前的相同,未初始化的变量 DatabaseEntity,然后是一个 IIFE,其中实际代码设置了 DatabaseEntity 对象的属性。这一次,虽然,还有另一个 IIFE。这个新的 IIFE 与 DatabaseEntity 命名空间的第二个声明相匹配。

现在,当执行第二个 IIFE 时,DatabaseEntity 已经绑定到一个对象,因此,我们只是通过添加额外属性来扩展已经可用的对象。

我们现在已经了解了 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:

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

为了解决这个问题,我们现在将为这个包创建一个类型声明文件。

首先,创建一个名为 types/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:

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

现在,如果我们返回原始代码,我们将看到错误已更改。TypeScript 编译器现在给出错误是2305:

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

当我们为 example-vector3 创建模块声明时,导出当前设置为空命名空间。 没有从该命名空间中导出 Vector3 类。

重新打开 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 存储库来使所有开发人员都可以使用它。

最后结论

在今天的教程中,我们了解了 TypeScript 中命名空间的基本语法,并检查了 TypeScript 编译器将其更改为的 JavaScript。

我们还尝试了命名空间的一个常见用例:为尚未键入的外部库提供环境类型。

虽然,不推荐使用命名空间,但并不总是建议在代码库中使用命名空间作为代码组织机制。现代代码应该使用 ES 模块语法,因为它具有命名空间提供的所有功能,并且从 ECMAScript 2015 开始,它成为规范的一部分。

但是,在创建模块声明时,仍然建议使用命名空间,因为它允许更简洁的类型声明。

如果你还想阅读更多有关 TypeScript 的教程文章,请看下面的推荐阅读内容,如果你觉得我今天的教程不错,请点赞我,关注我,并将这篇文章分享给你的朋友,也许能够帮助到他。

最后,感谢你的阅读,编程快乐!

本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/n8FeRL6LmYgKEwRi6naX1g

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237225次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8060次阅读
 目录