我有一些代码:

baseTypes.ts

export namespace Living.Things {
  export class Animal {
    move() { /* ... */ }
  }
  export class Plant {
    photosynthesize() { /* ... */ }
  }
}

dog.ts

import b = require('./baseTypes');

export namespace Living.Things {
  // Error, can't find name 'Animal', ??
  export class Dog extends Animal {
    woof() { }
  }
}

tree.ts

// Error, can't use the same name twice, ??
import b = require('./baseTypes');
import b = require('./dogs');

namespace Living.Things {
  // Why do I have to write b.Living.Things.Plant instead of b.Plant??
  class Tree extends b.Living.Things.Plant {

  }
}

这让人很困惑。我想有一堆外部模块,它们都向同一个命名空间Living.Things贡献类型。这似乎根本不管用——我在dogs.ts中看不到Animal。我必须写出完整的命名空间名字b。生物。植物。树。它不能跨文件在同一个名称空间中组合多个对象。我怎么做呢?


当前回答

组织代码的正确方法是使用单独的目录来代替名称空间。每个类都在它自己的文件中,在它各自的命名空间文件夹中。索引。Ts只会重新导出每个文件;索引中不应包含实际代码。ts文件。以这样的方式组织您的代码使得导航更加容易,并且是基于目录结构的自文档化。

// index.ts
import * as greeter from './greeter';
import * as somethingElse from './somethingElse';

export {greeter, somethingElse};

// greeter/index.ts
export * from './greetings.js';
...

// greeter/greetings.ts
export const helloWorld = "Hello World";

然后你可以这样使用它:

import { greeter } from 'your-package'; //Import it like normal, be it from an NPM module or from a directory.
// You can also use the following syntax, if you prefer:
import * as package from 'your-package';

console.log(greeter.helloWorld);

其他回答

白化病小改善答案:

base.ts

export class Animal {
move() { /* ... */ }
}

export class Plant {
  photosynthesize() { /* ... */ }
}

dog.ts

import * as b from './base';

export class Dog extends b.Animal {
   woof() { }
} 

things.ts

import { Dog } from './dog'

namespace things {
  export const dog = Dog;
}

export = things;

main.ts

import * as things from './things';

console.log(things.dog);

Ryan的回答没有错,但是对于那些来这里寻找如何在正确使用ES6名称空间的情况下维护一个类一个文件结构的人,请参考来自微软的有用资源。

在阅读文档后,我不清楚的一件事是:如何用一个导入导入整个(合并的)模块。

编辑 转回来更新这个答案。TS中出现了几种命名空间的方法。

所有模块类都在一个文件中。

export namespace Shapes {
    export class Triangle {}
    export class Square {}      
}

将文件导入命名空间,并重新分配

import { Triangle as _Triangle } from './triangle';
import { Square as _Square } from './square';

export namespace Shapes {
  export const Triangle = _Triangle;
  export const Square = _Square;
}

// ./shapes/index.ts
export { Triangle } from './triangle';
export { Square } from './square';

// in importing file:
import * as Shapes from './shapes/index.ts';
// by node module convention, you can ignore '/index.ts':
import * as Shapes from './shapes';
let myTriangle = new Shapes.Triangle();

最后一点考虑。您可以为每个文件命名空间

// triangle.ts
export namespace Shapes {
    export class Triangle {}
}

// square.ts
export namespace Shapes {
    export class Square {}
}

但是当一个从同一个名称空间导入两个类时,TS会抱怨标识符重复。此时唯一的解决方案是别名命名空间。

import { Shapes } from './square';
import { Shapes as _Shapes } from './triangle';

// ugh
let myTriangle = new _Shapes.Shapes.Triangle();

这种混叠是绝对可恶的,所以不要这样做。你最好采用上面的方法。就我个人而言,我更喜欢“桶”。

你可以使用*作为wrapper_var语法,使所有导入的方法都可以在wrapper_var下访问:

import * as validator from "./ZipCodeValidator";
let myValidator = new validator.ZipCodeValidator();

尝试按文件夹组织:

baseTypes.ts

export class Animal {
    move() { /* ... */ }
}

export class Plant {
    photosynthesize() { /* ... */ }
}

dog.ts

import b = require('./baseTypes');

export class Dog extends b.Animal {
    woof() { }
}   

tree.ts

import b = require('./baseTypes');

class Tree extends b.Plant {
}

LivingThings.ts

import dog = require('./dog')
import tree = require('./tree')

export = {
    dog: dog,
    tree: tree
}

main.ts

import LivingThings = require('./LivingThings');
console.log(LivingThings.Tree)
console.log(LivingThings.Dog)

这个想法是你的模块本身不应该关心/知道它们正在参与一个命名空间,但是这以一种紧凑、合理的方式将你的API暴露给消费者,而不知道你正在为项目使用哪种类型的模块系统。

糖果杯类比

版本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!)