如何将名称空间与TypeScript外部模块一起使用?


233

我有一些代码:

baseTypes.ts

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

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

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

// 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。看来,这并不在所有的工作-我看不到Animaldogs.ts。我要写完整的命名空间名称b.Living.Things.Planttree.ts。在整个文件的同一个命名空间中组合多个对象是行不通的。我该怎么做呢?

Answers:


859

糖果杯比喻

第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:无杯

让我们回到最初的例子,杯子AAA没有做你任何好处。相反,您可以将代码编写为:

Mod1.ts

export class Twix { ... }

Mod2.ts

export class PeanutButterCup { ... }

Mod3.ts

export class KitKat { ... }

创建一个看起来像这样的图片:

在此处输入图片说明

好多了!

现在,如果您仍在考虑要在模块中真正使用名称空间的内容,请继续阅读...


这些不是您要寻找的概念

我们需要回到为什么命名空间首先存在的起源,并检查这些原因对于外部模块是否有意义。

组织:命名空间非常便于将逻辑相关的对象和类型组合在一起。例如,在C#中,您将在中找到所有集合类型System.Collections。通过将我们的类型组织到分层名称空间中,我们为这些类型的用户提供了良好的“发现”体验。

名称冲突:命名空间对于避免命名冲突很重要。例如,你可能有My.Application.Customer.AddFormMy.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 classexport function没有单个文件的文件export default
  • 多个export module Foo {在顶级具有相同文件的文件(不要认为这些文件将合并为一个文件Foo!)

80
这是无答案的。您不需要或不需要外部模块名称空间的前提是错误的。虽然文件系统是一种可以组织方案的还挺用于这些目的,它是几乎没有很好的为消费者有n个利用从给定的项目N个类别或功能import语句; 尤其是因为当您实际编写代码时,它还会混淆命名约定。
Albinofrenchy

12
无论人们想要多少,这仍然是不可能的
Ryan Cavanaugh 2015年

26
我不明白,我们不再写Pascal了。从什么时候开始使用文件系统进行组织?
大卫,

9
您可以通过一个“包装器”模块来导入和重新导出库的使用者感兴趣的所有内容。但是,再次强调,使用“命名空间”将不会为强制使用您代码的任何人提供另一种间接访问级别而提供任何值。
Ryan Cavanaugh

13
很棒的文章,谢谢。我觉得您应该从www.typescriptlang.org/docs/handbook/namespaces.html链接到此。我一定已经阅读了3到4次typescriptlang.org链接,作为C#开发人员,我自然希望将所有内容都放在一个命名空间中。我已经阅读了一些建议,但并没有解释为什么,也没有什么比这更明确(和充分描述)了。加上打字稿文档中没有任何内容提及此AFAIK
Adam Plocher

53

Ryan的回答没什么问题,但是对于那些来这里寻求如何在仍然正确使用ES6名称空间的同时维护一个文件一类结构的人们,请参考Microsoft的有用资源。

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

编辑 回圈以更新此答案。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();

这种混叠绝对是令人讨厌的,所以不要这样做。您最好采用上述方法。就个人而言,我更喜欢“桶”。


6
什么是“ ES6名称空间”?
Aluan Haddad '18

导入es2015 +时使用@AluanHaddad,导入的内容是默认的,解构的或命名空间的。const fs = require('fs')fs是名称空间。import * as moment from 'moment'moment是名称空间。这是本体,而不是规范。
Jefftopia

我知道这一点,但您最好在回答中加以解释。但是,ES6命名空间实际上是一回事,require由于许多原因该示例不适用于它们,包括可能无法调用ES6命名空间,同时require返回一个很容易调用的普通对象。
Aluan Haddad

1
我不理解,因为从逻辑上讲,无论导入的东西是否可调用,它仍然可以用作命名空间。我认为上述警告对我的回答没有实质意义。
Jefftopia '18

7

尝试按文件夹组织:

baseTypes.ts

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

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

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

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

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

class Tree extends b.Plant {
}

LivingThings.ts

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

export = {
    dog: dog,
    tree: tree
}

主要

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

这个想法是,您的模块本身不应该在意/知道它们正在参与名称空间,但是这会以一种紧凑,明智的方式向您的用户展示API,这与您在项目中使用的模块系统类型无关。


8
LivingThings.dog.Dog是这里的一切。
科里·阿利克斯

我建议保持字母大小写一致,如果导出“ Tree”,然后导入“ Tree”,而不是“ tree”。
demisx

1
另外,tree.ts如果根本没有导出的成员,如何从中导入呢?
demisx

Man TS肯定有一些愚蠢的旧语法,就像importrequire在一个语句中一起。
安迪

3

Albinofrenchy的小改进答案:

基本

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

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

import * as b from './base';

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

东西

import { Dog } from './dog'

namespace things {
  export const dog = Dog;
}

export = things;

主要

import * as things from './things';

console.log(things.dog);

2
谢谢你!只是想说,对现有答案所做的更改最好不作为新答案发布:应该将它们添加为对现有答案的注释,或者(更好)通过建议对您希望的答案进行编辑来建议提高。
a3nm

3

OP,我和你在一起。同样,获得300+票的答案也没有错,但是我的看法是:

  1. 将类分别放入舒适的温暖自己的文件中有什么问题?我的意思是,这会使情况看起来好很多,对吗?(或所有模型都像1000行文件的人)

  2. 因此,如果要实现第一个,我们必须导入import import import ...仅在每个模型文件(例如man,srsly,模型文件,.d.ts文件)中导入,为什么会有那么多*在那儿?它应该简单,整洁,仅此而已。为什么在那里需要进口?为什么?C#得到名称空间是有原因的。

  3. 届时,您将使用“ filenames.ts”作为标识符。作为标识符...现在就来2017年,我们仍然这样做吗?伊玛回到火星,再睡1000年。

因此,可悲的是,我的答案是:不,如果您不使用所有这些导入或不使用这些文件名作为标识符(我认为这很愚蠢),则无法使“名称空间”功能正常运行。另一个选择是:将所有这些依赖项放入一个名为filenameasidentifier.ts的框中,然后使用

export namespace(or module) boxInBox {} .

将它们包装起来,这样当他们只是试图从该类的顶部获取引用时,他们就不会尝试访问具有相同名称的其他类。


3

我在这个主题上看到的几个问题/评论对我来说似乎是该人正在使用的Namespace意思是“模块别名”的地方。正如Ryan Cavanaugh在他的评论中提到的那样,您可以有一个“包装器”模块,重新导出几个模块。

如果您真的想从相同的模块名称/别名导入所有文件,请将包装器模块与中的路径映射结合在一起tsconfig.json

例:

./path/to/CompanyName.Products/Foo.ts

export class Foo {
    ...
}


./path/to/CompanyName.Products/Bar.ts

export class Bar {
    ...
}


./path/to/CompanyName.Products/index.ts

export { Foo } from './Foo';
export { Bar } from './Bar';



tsconfig.json

{
    "compilerOptions": {
        ...
        paths: {
            ...
            "CompanyName.Products": ["./path/to/CompanyName.Products/index"],
            ...
        }
        ...
    }
    ...
}



main.ts

import { Foo, Bar } from 'CompanyName.Products'

注意:需要以某种方式处理输出.js文件中的模块分辨率,例如使用此https://github.com/tleunen/babel-plugin-module-resolver

.babelrc处理别名解析的示例:

{
    "plugins": [
        [ "module-resolver", {
            "cwd": "babelrc",
            "alias": {
                "CompanyName.Products": "./path/to/typescript/build/output/CompanyName.Products/index.js"
            }
        }],
        ... other plugins ...
    ]
}

1

试试这个名称空间模块

namespaceModuleFile.ts

export namespace Bookname{
export class Snows{
    name:any;
    constructor(bookname){
        console.log(bookname);
    }
}
export class Adventure{
    name:any;
    constructor(bookname){
        console.log(bookname);
    }
}
}





export namespace TreeList{
export class MangoTree{
    name:any;
    constructor(treeName){
        console.log(treeName);
    }
}
export class GuvavaTree{
    name:any;
    constructor(treeName){
        console.log(treeName);
    }
}
}

bookTreeCombine.ts

---编译部分-

import {Bookname , TreeList} from './namespaceModule';
import b = require('./namespaceModule');
let BooknameLists = new Bookname.Adventure('Pirate treasure');
BooknameLists = new Bookname.Snows('ways to write a book'); 
const TreeLis = new TreeList.MangoTree('trees present in nature');
const TreeLists = new TreeList.GuvavaTree('trees are the celebraties');

0

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

export module Living.Things {
    // Error, can't find name 'Animal', ??
    // Solved: can find, if properly referenced; exporting modules is useless, anyhow
    export class Dog extends b.Living.Things.Animal {
        public woof(): void {
            return;
        }
    }
}

// Error, can't use the same name twice, ??
// Solved: cannot declare let or const variable twice in same scope either: just use a different name
import b = require('./baseTypes');
import d = require('./dog');

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

-1

组织代码的正确方法是使用单独的目录代替名称空间。每个类将位于其自己的文件中,位于其各自的命名空间文件夹中。index.ts将仅重新导出每个文件;index.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);

这具有误导性,并且绝对不正确。这不是名称空间的工作方式。此外,它也无法回答操作问题。
AndrewMcLagan
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.