糖果杯比喻
版本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
:
虽然这个设置很有用,但它在模块的情况下不适用(因为模块不会污染全局作用域)。
第三版:无杯
回到原始示例,杯子 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
模块。
名称冲突:这在外部模块中根本不适用。在模块内部,没有两个对象具有相同的名称的情况。从消费者的角度来看,消费者任何给定模块都可以选择用于引用模块的名称,因此不可能发生意外的命名冲突。
即使您认为模块如何工作并没有充分解决那些原因,试图在外部模块中使用命名空间的“解决方案”甚至都行不通。
盒中的盒中的盒
一个故事:
Your friend Bob has come up with a new organization scheme for his house, but it is not very useful. He has labeled 60 boxes in the pantry, each with the same label "Pantry". Inside each box, there is another box labeled "Grains", and inside that box, there is another box labeled "Pasta". Finally, inside the "Pasta" box, you can find the specific type of pasta, such as penne or rigatoni. However, this system requires opening many boxes to access any particular item, making it less convenient than simply storing everything on a shelf. When you question Bob about his system, he explains that he created a namespace for all his pasta to prevent others from putting non-pasta items in the "Pantry" namespace. However, his system is unnecessarily complicated and confusing.
In programming, external modules are like their own boxes. Each module contains its own functionality and should be organized accordingly. Instead of using namespaces, it is better to organize modules based on their functionality.
When organizing modules, it is best to export them as close to the top-level as possible. If you are only exporting a single class or function, use "export default". For example:
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();
红旗标志
以下所有内容都是模块结构的红旗标志。如果您的文件符合以下任何一项,请仔细检查,确保您没有尝试将外部模块命名空间化:
- 一个文件的唯一顶级声明是
export module Foo { ... }
(删除 Foo
并将所有内容“上移”一级)
- 一个文件只有一个
export class
或 export function
,而不是 export default
- 多个文件在顶层具有相同的
export module Foo {
(不要认为这些会合并为一个 Foo
!)