糖果杯比喻
第1版:每个糖果都有一个杯子
假设您编写了以下代码:
Mod1.ts
export namespace A {
export class Twix { ... }
}
Mod2.ts
export namespace A {
export class PeanutButterCup { ... }
}
Mod3.ts
export namespace A {
export class KitKat { ... }
}
您已创建此设置:
每个模块(纸片)都有一个名为的杯子A
。这没用-您实际上不是在这里整理糖果,只是在您和零食之间添加了一个额外的步骤(将其从杯子中取出)。
版本2:全球范围内一杯
如果您不使用模块,则可以编写如下代码(请注意缺少export
声明):
global1.ts
namespace A {
export class Twix { ... }
}
global2.ts
namespace A {
export class PeanutButterCup { ... }
}
global3.ts
namespace A {
export class KitKat { ... }
}
这段代码A
在全局范围内创建了一个合并的名称空间:
此设置很有用,但不适用于模块(因为模块不会污染全局范围)。
版本3:无杯
让我们回到最初的例子,杯子A
,A
和A
没有做你任何好处。相反,您可以将代码编写为:
Mod1.ts
export class Twix { ... }
Mod2.ts
export class PeanutButterCup { ... }
Mod3.ts
export class KitKat { ... }
创建一个看起来像这样的图片:
好多了!
现在,如果您仍在考虑要在模块中真正使用名称空间的内容,请继续阅读...
这些不是您要寻找的概念
我们需要回到为什么命名空间首先存在的起源,并检查这些原因对于外部模块是否有意义。
组织:命名空间非常便于将逻辑相关的对象和类型组合在一起。例如,在C#中,您将在中找到所有集合类型System.Collections
。通过将我们的类型组织到分层名称空间中,我们为这些类型的用户提供了良好的“发现”体验。
名称冲突:命名空间对于避免命名冲突很重要。例如,你可能有My.Application.Customer.AddForm
和My.Application.Order.AddForm
-两种类型名称相同,但不同的命名空间。在所有标识符都存在于同一根作用域且所有程序集都加载所有类型的语言中,将所有内容都放在命名空间中至关重要。
这些原因在外部模块中有意义吗?
组织:外部模块已必存在于文件系统中。我们必须通过路径和文件名来解析它们,因此有一种逻辑上的组织方案可供我们使用。我们可以有一个/collections/generic/
带有list
模块的文件夹。
名称冲突:这根本不适用于外部模块。在一个模块内,没有任何理由使两个对象具有相同的名称。从使用方面来看,任何给定模块的使用者都可以选择他们将用来引用该模块的名称,因此,不可能发生意外的命名冲突。
即使您不相信模块的工作方式已充分解决了这些原因,尝试在外部模块中使用名称空间的“解决方案”甚至都不起作用。
盒子里的盒子盒子里的盒子
一个故事:
您的朋友鲍勃给您打电话。他说:“我家里有一个很棒的新组织方案”,“来看看!”。整洁,让我们看看鲍勃想出了什么。
您从厨房开始,打开厨房。有60个不同的框,每个框标记为“ Pantry”。您随机选择一个盒子并打开它。里面是一个标有“谷物”的盒子。您打开“谷物”框,找到一个标有“意大利面”的框。您打开“面食”框,找到一个标有“ Penne”的框。您打开这个盒子,然后按预期找到一袋通心粉。
有点困惑,您拿起一个相邻的框,也标记为“ Pantry”。里面是一个单独的盒子,再次标记为“谷物”。打开“谷物”框,然后再次找到一个标有“意大利面”的框。您打开“面食”框并找到一个框,该框标记为“ Rigatoni”。您打开此盒子,然后发现...一包通心粉通心粉。
“这很棒!” 鲍勃说。“一切都在名称空间中!”。
“但是鲍勃……”你回答。“您的组织方案没有用。您必须打开一堆箱子才能接触到任何东西,而要找到任何东西实际上比将所有东西都放在一个而不是三个箱子中要方便得多。事实上,食品储藏室已经按架子分类了,您根本不需要盒子。为什么不把意大利面放在架子上,并在需要时将其拿起呢?”
“您不明白-我需要确保没有其他人将不属于'Pantry'名称空间的内容放入其中。而且我已经将所有面食安全地组织到了该Pantry.Grains.Pasta
名称空间中,以便可以轻松找到它。”
鲍勃是个很困惑的人。
模块是他们自己的盒子
您可能在现实生活中也发生过类似的事情:您在Amazon上订购了几样东西,每个项目都显示在自己的盒子里,里面有一个较小的盒子,并且您的项目包装在自己的包装中。即使内部箱相似,也不能有效地将货物“合并”。
与盒子类似,关键的观察是外部模块是它们自己的盒子。它可能是一个非常复杂的项目,具有很多功能,但是任何给定的外部模块都是它自己的盒子。
外部模块指南
既然我们已经知道不需要使用“命名空间”,那么我们应该如何组织模块?遵循一些指导原则和示例。
导出尽可能接近顶层
- 如果仅导出单个类或函数,请使用
export default
:
MyClass.ts
export default class SomeType {
constructor() { ... }
}
我的功能
function getThing() { return 'thing'; }
export default getThing;
消费
import t from './MyClass';
import f from './MyFunc';
var x = new t();
console.log(f());
这对于消费者而言是最佳的。他们可以随心所欲地为您的类型命名(t
在这种情况下),而不必做任何多余的操作来查找您的对象。
MyThings.ts
export class SomeType { ... }
export function someFunc() { ... }
消费
import * as m from './MyThings';
var x = new m.SomeType();
var y = m.someFunc();
- 如果要导出大量内容,则仅应使用
module
/ namespace
关键字:
MyLargeModule.ts
export namespace Animals {
export class Dog { ... }
export class Cat { ... }
}
export namespace Plants {
export class Tree { ... }
}
消费
import { Animals, Plants} from './MyLargeModule';
var x = new Animals.Dog();
红旗
以下所有都是模块结构的危险标记。仔细检查您是否不想为外部模块命名,如果以下任何一种适用于您的文件:
- 唯一的顶层声明是的文件
export module Foo { ... }
(Foo
将所有内容“ 删除” 并向上移动)
- 具有单个
export class
或export function
没有单个文件的文件export default
- 多个
export module Foo {
在顶级具有相同文件的文件(不要认为这些文件将合并为一个文件Foo
!)