如何在TypeScript外部模块中使用名称空间?

时间:2015-05-20 18:27:16

标签: javascript module typescript

我有一些代码:

baseTypes.ts

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

dog.ts

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

export module 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');

module 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。看起来这根本不起作用 - 我在Animal中看不到dogs.ts。我必须在b.Living.Things.Plant中编写完整的命名空间名称tree.ts。它不会跨文件组合同一命名空间中的多个对象。我该怎么做?

10 个答案:

答案 0 :(得分:716)

糖果杯类比

版本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 { ... }
}

您已创建此设置: enter image description here

每个模块(纸张)都有自己的杯子,名为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

enter image description here

此设置很有用,但在模块的情况下不适用(因为模块不会污染全局范围)。

第3版:无精打采

回到原来的例子,杯子AAA并没有给你任何好处。相反,您可以将代码编写为:

<强> Mod1.ts

export class Twix { ... }

<强> Mod2.ts

export class PeanutButterCup { ... }

<强> Mod3.ts

export class KitKat { ... }

创建一个如下所示的图片:

enter image description here

好多了!

现在,如果您仍然在考虑您希望在模块中使用名称空间的程度,请继续阅读......

这些Aren是您正在寻找的概念

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

组织:命名空间可以方便地将逻辑相关的对象和类型组合在一起。例如,在C#中,您将在System.Collections中查找所有集合类型。通过将我们的类型组织成分层命名空间,我们提供了一个很好的发现和#34;这些类型的用户的经验。

名称冲突:命名空间对于避免命名冲突很重要。例如,您可能有My.Application.Customer.AddFormMy.Application.Order.AddForm - 两个名称相同但名称空间不同的类型。在所有标识符都存在于同一根范围中且所有程序集都加载所有类型的语言中,将所有内容都放在命名空间中至关重要。

这些原因在外部模块中有意义吗?

组织:外部模块必然存在于文件系统中。我们必须通过路径和文件名来解决它们,因此我们可以使用逻辑组织方案。我们可以有一个/collections/generic/文件夹,其中包含list个模块。

名称冲突:这在外部模块中根本不适用。 模块中,没有合理的理由让两个具有相同名称的对象。从消费方面来看,任何给定模块的使用者都会选择他们用来引用模块的名称,因此不可能发生意外命名冲突。

即使您不相信这些原因可以通过模块的工作方式得到充分解决,但&#34;解决方案&#34;试图在外部模块中使用命名空间甚至不起作用。

Boxes in Boxes

一个故事:

  

你的朋友鲍勃打电话给你。 &#34;我家里有一个很棒的新组织计划&#34;他说,&#34;来看看吧!&#34;。干得好,让我们看看鲍勃想出了什么。

     

你从厨房开始打开食品室。有60个不同的盒子,每个盒子标有&#34; Pantry&#34;。你随机挑选一个盒子然后打开它。里面是一个标有&#34; Grains&#34;的盒子。你打开&#34;谷物&#34;框中找到标有&#34; Pasta&#34;的单个方框。你打开&#34;意大利面&#34;框中找到标有&#34; Penne&#34;的单个方框。你打开这个盒子,然后找到一袋通心粉。

     

有点困惑,你拿起一个相邻的盒子,也标有&#34; Pantry&#34;。里面是一个单独的盒子,再次标记为&#34; Grains&#34;。你打开&#34;谷物&#34;然后,再次找到一个标有&#34; Pasta&#34;的盒子。你打开&#34;意大利面&#34;框中找到一个盒子,这个盒子标有&#34; Rigatoni&#34;。你打开这个盒子,找到一袋通心粉意大利面。

     

&#34;太棒了!&#34;鲍勃说。 &#34;一切都在名称空间中!&#34;。

     

&#34;但鲍勃......&#34;你回复。 &#34;您的组织方案毫无用处。你必须打开一堆盒子才能找到任何东西,找到任何东西实际上并不比你把所有东西放在一个框而不是中更方便3 。事实上,由于您的食品储藏室已经逐个分类,您根本不需要这些食品盒。为什么不把面食放在架子上并在需要时捡起来?&#34;

     

&#34;你不明白 - 我需要确保没有其他人放置一些不属于“食品”的食品。命名空间。而且我已经将所有意大利面安全地组织到Pantry.Grains.Pasta命名空间中,这样我就可以轻松找到它&#34;

     鲍勃是一个非常困惑的人。

模块是他们自己的盒子

你可能在现实生活中发生了类似的事情:你在亚马逊上订购了一些东西,每件物品都出现在自己的盒子里,里面有一个小盒子,你的物品用自己的包装包装。即使内部箱子相似,但出货也没有用,并且#34;组合在一起。

与盒子类比,关键的观察是外部模块是他们自己的盒子。它可能是一个非常复杂的项目,具有许多功能,但任何给定的外部模块都是它自己的框。

外部模块指南

现在我们已经发现我们不需要使用“命名空间”,我们应该如何组织模块?一些指导原则和例子如下。

尽可能靠近顶级出口

  • 如果您只导出单个类或函数,请使用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();

红旗

以下所有内容都是模块结构的红色标志。如果其中任何一个适用于您的文件,请仔细检查您是否尝试命名外部模块:

  • 一个文件,其唯一的顶级声明为export module Foo { ... }(删除Foo并移动所有内容&#39; up&#39; a level)
  • 包含export classexport function的{​​{1}} <{1}}
  • 的文件
  • 顶层具有相同export default的多个文件(不要认为这些文件会合并为一个export module Foo {!)

答案 1 :(得分:51)

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();

这种别​​名绝对令人憎恶,所以不要这样做。采用上述方法,你会变得更好。就个人而言,我更喜欢&#39;桶&#39;

答案 2 :(得分:6)

尝试按文件夹进行整理:

<强> 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暴露给消费者,这与你使用哪种类型的模块系统无关。该项目。

答案 3 :(得分:3)

Albinofrenchy的小动作答案:

<强> 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);

答案 4 :(得分:2)

我和你在一起男人。 同样,300+票数的答案也没有错,但我的意见是:

  1. 将课程单独放入舒适温暖的文件中有什么问题? 我的意思是这会让事情看起来好多了吧? (或者就像所有模型的1000行文件一样)

  2. 那么,如果第一个将实现,我们必须导入导入导入...导入只在每个模型文件中,如man,srsly,模型文件,.d.ts文件,为什么那里有那么多* s?它应该只是简单,整洁,而且就是这样。为什么我需要那里的进口?为什么? C#因为某个原因而获得了命名空间。

  3. 到那时,你实际上正在使用&#34; filenames.ts&#34;作为标识符。作为标识符...现在来2017年,我们仍然这样做?你回到火星再睡了1000年。

  4. 很遗憾,我的答案是:nop,你不能制作&#34;命名空间&#34;如果您不使用所有这些导入或使用这些文件名作为标识符(我认为这真的很愚蠢),那就是功能性的。另一种选择是:将所有这些依赖项放入名为filenameasidentifier.ts的框中并使用

    export namespace(or module) boxInBox {} .
    

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

答案 5 :(得分:2)

我在这个主题中看到的一些问题/评论听起来好像是这个人正在使用Namespace,他们的意思是“模块别名”。正如Ryan Cavanaugh在他的一条评论中提到的那样,你可以让一个'Wrapper'模块重新导出几个模块。

如果您确实要从同一模块名称/别名中导入所有内容,请将包装器模块与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 ...
    ]
}

答案 6 :(得分:0)

<强> dog.ts

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;
        }
    }
}

<强> tree.ts

// 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 {
    }
}

答案 7 :(得分:0)

尝试此名称空间模块

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');

答案 8 :(得分:0)

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

答案 9 :(得分:0)

您可以使用 * as wrapper_var 语法使所有导入的方法都可以在 wrapper_var 下访问:

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