我对 module/namespace/export
和 import, require, reference
的用法感到很困惑。来自Java背景,有人可以简单地解释一下什么时候使用什么以及什么是正确的设计?我在编写示例项目时感觉自己搞砸了
到目前为止,这是我的理解 1. module
用于外部包 2. namespace
用于内部包
没看懂怎么分类的?
何时导出类、命名空间或包?
如果我们导出包/命名空间,其中的所有类都被导出或需要显式导出
如何导入/需要它们中的每一个?
According to doc,如果我为每个经理/模型创建每个“ts”文件,Typescript 不建议使用“命名空间”吗?直接使用参考路径?
请详细解释,因为我来自不同的背景,不确定 ES6/ES5 等。
我看到有几个人提出/对相同的问题感到困惑。我希望有人可以用现实世界的场景详细解释
没看懂怎么分类的?
命名空间用于组织/封装您的代码。外部模块用于组织/封装您的代码并在运行时定位您的代码。在实践中,您在运行时有两种选择:1) 将所有转译的代码合并到一个文件中,或者 2) 使用外部模块并拥有多个文件,并且需要一些其他机制来获取这些文件。
何时导出类、命名空间或包?
要使类型或值在它所在的文件之外可见,如果它位于命名空间内,则必须将其导出。无论您是在顶层还是在命名空间中导出它,都将决定它现在是否在外部模块中。
如果我们导出包/命名空间,其中的所有类都被导出或需要显式导出
命名空间中的类总是需要显式导出,以便该类在编译时在定义它的文件之外可见。
如何导入/需要它们中的每一个?
这取决于您是否使用外部模块。始终需要导入外部模块才能“使用”它。导入不在外部模块中的命名空间实际上只是为命名空间提供别名——您仍然必须在类型/任何内容前加上别名(这就是为什么您通常不希望将命名空间与外部模块一起使用;这样做意味着您在引用外部模块提供的任何内容时始终必须使用前缀。)不在外部模块中的命名空间可以跨越文件,因此如果您在同一个命名空间中,则可以引用由命名空间,不需要任何类型的导入。
要真正理解上述内容,您需要一些背景知识。理解引用/命名空间/外部模块的关键是这些结构在编译时做什么以及它们在运行时做什么。
引用指令在编译时用于定位类型信息。您的来源中有一个特定的符号。 TypeScript 编译器如何定位该符号的定义?引用指令在很大程度上已被 tsconfig.json 机制所包含——使用 tsconfig.json,您可以告诉编译器您的所有源代码在哪里。
命名空间可以包含类型定义和/或实现。如果命名空间只包含类型信息,那么它根本没有运行时表现——您可以通过查看 JS 输出并找到一个空的 JS 文件来检查这一点。如果命名空间有实现代码,那么代码会包装在一个闭包中,该闭包分配给一个与命名空间同名的全局变量。使用嵌套命名空间,根命名空间将有一个全局变量。再次检查 JS 输出。从历史上看,命名空间是 JS 客户端库试图避免命名冲突问题的方式。这个想法是将整个库包装到一个闭包中,然后尽可能少地公开全局占用空间——只有一个引用闭包的全局变量。好吧,问题仍然是您在全局空间中声明了一个名称。如果你想要一个库的两个版本怎么办? TypeScript 命名空间仍然存在如何定位命名空间源的问题。也就是说,引用 AB 的源代码仍然存在告诉编译器如何定位 AB 的问题——通过使用引用指令或使用 tsconfig.json。或者通过将命名空间放入外部模块,然后导入外部模块。
外部模块起源于服务器端 JS。外部模块与文件系统上的文件是一一对应的。您可以使用文件系统目录结构将外部模块组织成嵌套结构。导入外部模块通常会引入对该外部模块的运行时依赖(例外情况是当您导入外部模块但在值位置不使用其任何导出 - 也就是说,您只导入外部模块获取其类型信息)。外部模块隐含在闭包中,这是关键:模块的用户可以将闭包分配给他们想要的任何局部变量。 TypeScript/ES6 围绕将外部模块的导出映射到本地名称添加了额外的语法,但这只是一个细节。在服务器端,定位外部模块相对简单:只需在本地文件系统上定位代表外部模块的文件即可。如果您想在客户端使用外部模块,在浏览器中,它会变得更加复杂,因为没有等效于具有可加载模块的文件系统。所以现在在客户端,你需要一种方法将所有这些文件打包成一个可以在浏览器中远程使用的表单——这就是像 Webpack 这样的模块打包器(尽管 Webpack 比打包模块做的要多得多)和Browserify 开始发挥作用。模块捆绑器允许在浏览器中对外部模块进行运行时解析。
真实世界场景:AngularJS。假装外部模块不存在,使用单个命名空间来限制全局空间的污染(在下面的示例中,单个变量 MyApp 就是全局空间中的全部),仅导出接口,并使用 AngularJS 依赖注入进行实现可供使用。将所有类放在一个目录根目录下,将 tsconfig.json 添加到根目录,在同一目录根目录下安装 angularjs Typings,以便 tsconfig.json 也可以选择它,将所有输出合并到一个 JS 文件中。如果代码重用不是什么大问题,这对于大多数项目来说都可以正常工作。
我的服务.ts:
namespace MyApp {
// without an export the interface is not visible outside of MyService.ts
export interface MyService {
....
}
// class is not exported; AngularJS DI will wire up the implementation
class MyServiceImpl implements MyService {
}
angular.module("MyApp").service("myService", MyServiceImpl);
}
MyController.ts:
namespace MyApp {
class MyController {
// No import of MyService is needed as we are spanning
// one namespace with multiple files.
// MyService is only used at compile time for type checking.
// AngularJS DI is done on the name of the variable.
constructor(private myService: MyService) {
}
}
angular.module("MyApp").controller("myController", MyController);
}
使用 IIFE 避免污染全局运行时范围。在此示例中,根本没有创建全局变量。 (假定为 tsconfig.json。)
脚:
namespace Foo {
// without an export IFoo is not visible. No JS is generated here
// as we are only defining a type.
export interface IFoo {
x: string;
}
}
interface ITopLevel {
z: string;
}
(function(){
// export required above to make IFoo visible as we are not in the Foo namespace
class Foo1 implements Foo.IFoo {
x: string = "abc";
}
// do something with Foo1 like register it with a DI system
})();
酒吧.ts:
// alias import; no external module created
import IFoo = Foo.IFoo;
(function() {
// Namespace Foo is always visible as it was defined at
// top level (outside of any other namespace).
class Bar1 implements Foo.IFoo {
x: string;
}
// equivalent to above
class Bar2 implements IFoo {
x: string;
}
// IToplevel is visible here for the same reason namespace Foo is visible
class MyToplevel implements ITopLevel {
z: string;
}
})();
使用 IIFE,您可以避免在第一个示例中将 MyApp 作为全局变量引入。
我的服务.ts:
interface MyService {
....
}
(function() {
class MyServiceImpl implements MyService {
}
angular.module("MyApp").service("myService", MyServiceImpl);
})();
MyController.ts:
(function() {
class MyController {
constructor(private myService: MyService) {
}
}
angular.module("MyApp").controller("myController", MyController);
})();
有两件事:
TypeScript 中的模块是一个标准的 ES6 概念,它在代码的顶层使用导入/导出关键字;
命名空间是 TypeScript 特有的概念,用于帮助以过时的方式组织代码。
命名空间
这几乎是一个过时的概念。在 ES6 模块之前,在浏览器中分离 JavaScript 代码的常用方法是创建全局变量。例如,像 underscore 这样的 API 的所有函数都位于名为 _
的全局变量中。
这种旧方法类似于 Java 包或 PHP 命名空间。它不适应网络。新的 ECMAScript 标准解决了以下问题:如何使用两个同名的库?如何使用同一个库的两个不同版本?
注意:在 ECMAScript 定义“模块”(2014 年夏季)之前的 TypeScript 版本中,命名空间被称为“内部模块”,模块被称为“外部模块”。
注意 2:namespace
中的关键字 export
是该关键字的非标准 TypeScript 用法。它是一种声明可从命名空间外部公开访问的事物的方法。
ES6 模块
模块是在代码顶层包含关键字 import
或 export
的文件。
TypeScript 遵循 ECMAScript 的标准。我建议阅读 Mozilla 的一篇文章中的 a good introduction to ES6 modules。
如果您想在前端应用程序(在浏览器中)中使用模块,则必须使用捆绑程序(Webpack [the documentation here]、Browserify)或加载程序(SystemJS [a tutorial here] , RequireJS) 并在此环境中配置 TypeScript。
如果您的代码在 Node.js 中执行,只需配置 TypeScript 编译器以生成 CommonJS 格式。
注意:命名空间可以在模块中声明。在这种情况下,它将无法从模块外部作为全局变量访问。但是,它可以从模块中导出。
模块用于外部包 2. 命名空间用于内部包
实际上,module
关键字已替换为 namespace
关键字。
因此,更好的说法是模块是过去称为外部模块的东西,命名空间是过去称为内部模块的东西。
更多的
希望这有助于进一步:https://basarat.gitbooks.io/typescript/content/docs/project/modules.html
“require”和“import”在功能上是等价的。我们可以互换使用它们,因为我们的编译器并不真正关心浏览器是否支持它们。但是,虽然“require”起源于从 CommonJS 到 2009 年的旧编码风格,但“import”是从广泛接受的 ES6 (ES2015) 语法中派生的。所以你应该对新项目使用“import”而不是“require”。
命名混乱
在 Typescript 的早期,命名空间被称为内部模块,而 ES6 模块被称为外部模块。
现在,为了声明命名空间,Typescript 团队建议使用 namespace { }
而不是 module { }
语法,以避免与外部模块的命名混淆。因为外部模块现在只是“模块”,而内部模块是“命名空间”。
命名空间
宣言
Typescript 中的命名空间可以使用 namespace
或 module
关键字声明。两个关键字都做同样的事情。然后我们可以决定使用 export
关键字公开命名空间的哪一部分。
// LivingThings.ts
export namespace Animals {
export class Dog { }
export class Cat { }
}
export namespace Plants {
export class Orchid { }
export class Bamboo { }
}
// LivingThingsUser.ts
import { Animals, Plants } from "./LivingThings"
逻辑分组
在 ES6 之前,Typescript 中使用命名空间来封装接口、类、函数和变量,以支持一组相关功能并隐藏实现细节。这样我们可以防止变量泄漏到全局空间中。这有助于更好地组织代码并防止名称冲突。现在推荐使用 ES6 模块来实现这一点。
命名空间现在用于环境命名空间声明。
单个文件使用
我们可以跨多个文件声明命名空间,并且可以使用 --outFile
标志将它们连接起来。然后,我们可以在 HTML 页面的 <script>
标记内使用该连接文件。这允许我们在包含所有依赖项的客户端 Web 应用程序中以良好的方式构建我们的代码。
模块
宣言
模块也称为 ES6 模块。我们使用多个文件对相关功能进行分组,并仅使用 export
关键字使所需对象公开可见。
// Animals.ts
export class Dog { }
export class Cat { }
// Plants.ts
export class Orchid { }
export class Bamboo { }
// LivingThingsUser.ts
import { Dog, Cat } from "./Animals"
import { Orchid, Bamboo } from "./Plants"
逻辑分组
模块中的逻辑分组是通过使用单独的文件对相关功能进行分组来实现的。因此,外部模块也称为文件模块。
单个文件使用
我们不使用 <script>
标记加载客户端 Web 应用程序的模块,因为浏览器可能会在下载如此多的文件并同时呈现页面时变得迟缓。为此,我们使用 CommonJS、AMD、SystemJS 等模块加载器,它们使我们能够异步加载文件或将外部模块文件连接到单个优化文件中。
对于服务器端,尤其是在 Node.js 中,强烈推荐使用这些模块。
而已!