【问题标题】:Namespace declaration命名空间声明
【发布时间】:2018-09-22 20:10:28
【问题描述】:

在 TypeScript 文档中,namespaces 被称为内部 TypeScript 模块,为了在单个文件中使用它们作为引用,我们必须在命名空间中添加一个 ref 标头,例如:/// <reference path="Validation.ts" />,如文档中所述。但是,我们也可以使用 export declare namespace MyNameSpace 导出和声明命名空间,然后将其导入任何其他实体。是否有任何理由建议我们使用 header ref 标签而不是命名空间声明的导出。

在听到关于编译和运行时顺序的第一个答案后,我想也许最好提供一个示例,这是一个简单的问候应用程序,其中包含三个带有声明命名空间 的 mains 文件(我怀疑我们甚至需要当我们想在我们的应用程序范围内使用它时导入)

文件名: GreetingApp.ts

export declare namespace GreetingApp {}

文件名: Hello.ts

namespace GreetingApp {
    export class Hello {
        constructor(private name: string) {
            this.name = name.toLowerCase().charAt(0).toUpperCase() + name.substr(1).toLowerCase();
        }

        public toString(): string {
            return "Hello! " + this.name;
        }
    }
}

文件名: Bye.ts

namespace GreetingApp {
    export class Bye {
        constructor(private name: string) {
            this.name = name.toLowerCase().charAt(0).toUpperCase() + name.substr(1).toLowerCase();
        }

        public toString(): string {
            return "Goodbye! " + this.name;
        }
    }
}

文件名: UseCase.ts (用于导入和实例化对象的测试文件)

import GreetingApp from './GreetingApp';

console.log(new GreetingApp.Hello('adam').toString());
console.log(new GreetingApp.Bye('adam').toString());

【问题讨论】:

  • 为确保我理解正确,请您标记代码块的哪一部分对应于每个文件?
  • 确定我更新了问题并为每个文件分离了源代码

标签: typescript


【解决方案1】:

如果您不使用导出/导入,那么您的所有文件都在全局范围内运行,并且如果多个文件声明具有相同名称的命名空间(如您链接的示例中所示),则命名空间将被合并。这种方法可以很方便;它用于 TypeScript 编译器源代码本身以及我的一个项目中。 (TypeScript 团队声称外部模块优于命名空间,但当他们将编译器从命名空间迁移到外部模块时我会相信他们!)请注意,/// <reference path="..."> 有两个目的:在编译时从目标文件加载声明并确保在运行时在当前文件之前加载目标文件。如果您同时满足以下两个条件,您将能够省略 /// <reference path="...">:您使用 tsconfig.json 文件以确保在编译时加载所有文件,并且运行时加载顺序无关紧要。运行时加载顺序可能很重要的最常见原因之一是您定义了一个类,然后在不同的文件中扩展它。

如果您导出和导入命名空间,那么您正在使用外部模块,并且如果您在不同的外部模块中声明两个具有相同名称的命名空间,它们将不会合并。一个外部模块可以包含一个模块扩充,它将声明添加到另一个外部模块的命名空间,但不包含运行时代码。所以这不如在全局范围内运行的文件中合并命名空间方便。

第二轮

UseCase.ts 中,我假设您打算使用import { GreetingApp } from './GreetingApp',因为import GreetingApp from './GreetingApp' 给了我一个编译错误。 (如果是,请更新问题。)

哇,您发现了我以前不知道的 TypeScript 名称解析的疯狂方面。在这个例子中,Hello.tsBye.ts 是全局文件而不是外部模块(因为它们不包含顶级 ES6 导入或导出),因此它们都有助于全局范围内的 GreetingApp 命名空间,它与使用从GreetingApp.ts 导出的GreetingApp 命名空间。

那么UseCase.ts 发生了什么?如here 所述,TypeScript 符号可以具有以下一种或多种含义:值(包括值的命名空间)、类型和“命名空间”(表示类型的命名空间)。 GreetingApp.ts 中的 GreetingApp 命名空间只有命名空间含义,因为它不包含任何值,而全局 GreetingApp 命名空间具有值含义和命名空间含义,因为它包含由两个值组成的类(构造函数)和类型(实例类型)。

当引用一个符号时,引用的上下文决定了使用三种含义中的哪一种:如果使用X.Y作为值,则使用X的值含义并取其属性Y ,而如果使用X.Y作为类型,则使用X的命名空间含义和XY的类型含义。此外,符号的阴影是按意思工作的!当UseCase.ts./GreetingApp 导入GreetingApp 时,它导入了GreetingApp.ts 中的GreetingApp 所具有的唯一含义,即命名空间含义,遮蔽了全局GreetingApp 的命名空间含义。全局GreetingApp 的值含义仍然可见,并由GreetingApp.HelloGreetingApp.Bye 的引用使用。但是,您会注意到,如果您尝试声明 GreetingApp.Hello 类型的变量,它不起作用,因为该引用使用 GreetingApp 的命名空间含义,它解析为来自 GreetingApp.ts 的空命名空间而不是全局命名空间。

我知道这个分析是非常技术性的,但我希望我已经明确说明这个例子没有像你想象的那样工作(我希望下一个被 TypeScript 未充分记录的阴影行为迷惑的人会找到这个线程在网络搜索中)。导入和整个GreetingApp.ts 文件在示例中没有任何作用。如果您同时删除它们,那么您将使用“具有合并命名空间的全局文件”方法来构建我在原始答案中描述的代码库。

【讨论】:

  • 好的,在这种情况下,让我们开始实践吧,我创建了一个简单的应用程序来声明命名空间,并用一个示例更新了我原来的问题,供未来的 TypeScript 爱好者使用。
猜你喜欢
  • 2011-01-31
  • 1970-01-01
  • 2013-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-30
  • 2011-01-27
  • 2011-06-14
相关资源
最近更新 更多