糖果杯类比
版本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:全球范围内的一杯
如果你不使用模块,你可能会写这样的代码(注意没有导出声明):
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/文件夹。
名称冲突:这在外部模块中根本不适用。在一个模块中,没有合理的理由让两个对象具有相同的名称。从消费端来看,任何给定模块的消费者都可以选择用于引用该模块的名称,因此不可能出现意外的命名冲突。
即使您不相信模块的工作方式能够充分解决这些原因,尝试在外部模块中使用名称空间的“解决方案”也不起作用。
盒子里的盒子里的盒子
一个故事:
Your friend Bob calls you up. "I have a great new organization scheme in my house", he says, "come check it out!". Neat, let's go see what Bob has come up with.
You start in the kitchen and open up the pantry. There are 60 different boxes, each labelled "Pantry". You pick a box at random and open it. Inside is a single box labelled "Grains". You open up the "Grains" box and find a single box labelled "Pasta". You open the "Pasta" box and find a single box labelled "Penne". You open this box and find, as you expect, a bag of penne pasta.
Slightly confused, you pick up an adjacent box, also labelled "Pantry". Inside is a single box, again labelled "Grains". You open up the "Grains" box and, again, find a single box labelled "Pasta". You open the "Pasta" box and find a single box, this one is labelled "Rigatoni". You open this box and find... a bag of rigatoni pasta.
"It's great!" says Bob. "Everything is in a namespace!".
"But Bob..." you reply. "Your organization scheme is useless. You have to open up a bunch of boxes to get to anything, and it's not actually any more convenient to find anything than if you had just put everything in one box instead of three. In fact, since your pantry is already sorted shelf-by-shelf, you don't need the boxes at all. Why not just set the pasta on the shelf and pick it up when you need it?"
"You don't understand -- I need to make sure that no one else puts something that doesn't belong in the 'Pantry' namespace. And I've safely organized all my pasta into the Pantry.Grains.Pasta namespace so I can easily find it"
Bob is a very confused man.
模块是它们自己的盒子
你可能在现实生活中遇到过类似的事情:你在亚马逊上订购了一些东西,每件商品都有自己的盒子,里面有一个小盒子,你的商品用自己的包装包装着。即使内部箱子是相似的,货物也不是有效的“组合”。
使用盒子类比,关键的观察是外部模块是它们自己的盒子。它可能是一个具有许多功能的非常复杂的项目,但任何给定的外部模块都是它自己的盒子。
外部模块指南
既然我们已经知道不需要使用“名称空间”,那么我们应该如何组织模块呢?以下是一些指导原则和例子。
导出尽可能接近顶级
如果你只导出一个类或函数,使用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),并且不需要做任何多余的点来查找你的对象。
如果你导出多个对象,把它们都放在顶层:
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();
红旗
下面所有这些都是模块结构化的危险信号。仔细检查你是否试图命名外部模块,如果这些适用于你的文件:
该文件的唯一顶级声明是导出模块Foo{…}(删除Foo并将所有内容“向上”一个级别)
只有一个导出类或导出函数而不是默认导出的文件
在顶层有相同导出模块Foo{的多个文件(不要认为这些会合并成一个Foo!)