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