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

Rya*_*ugh 215 javascript module typescript

我有一些代码:

baseTypes.ts

export namespace Living.Things {
  export class Animal {
    move() { /* ... */ }
  }
  export class Plant {
    photosynthesize() { /* ... */ }
  }
}
Run Code Online (Sandbox Code Playgroud)

dog.ts

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

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

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 {

  }
}
Run Code Online (Sandbox Code Playgroud)

这一切都非常令人困惑.我想让一堆外部模块为同一个命名空间贡献类型Living.Things.看来,这并不在所有的工作-我看不到Animaldogs.ts.我要写完整的命名空间名称b.Living.Things.Planttree.ts.它不适用于跨文件在同一命名空间中组合多个对象.我该怎么做呢?

Rya*_*ugh 785

糖果杯比喻

版本1:每个糖果的杯子

假设你写了一些像这样的代码:

Mod1.ts

export namespace A {
    export class Twix { ... }
}
Run Code Online (Sandbox Code Playgroud)

Mod2.ts

export namespace A {
    export class PeanutButterCup { ... }
}
Run Code Online (Sandbox Code Playgroud)

Mod3.ts

export namespace A {
     export class KitKat { ... }
}
Run Code Online (Sandbox Code Playgroud)

您已创建此设置: 在此输入图像描述

每个模块(纸张)都有自己的杯子命名A.这没用 - 你实际上并没有在这里组织你的糖果,你只是在你和点心之间增加一个额外的步骤(从杯子里取出).


版本2:全球范围内的一杯

如果你没有使用模块,你可能会编写这样的代码(注意缺少export声明):

global1.ts

namespace A {
    export class Twix { ... }
}
Run Code Online (Sandbox Code Playgroud)

global2.ts

namespace A {
    export class PeanutButterCup { ... }
}
Run Code Online (Sandbox Code Playgroud)

global3.ts

namespace A {
     export class KitKat { ... }
}
Run Code Online (Sandbox Code Playgroud)

代码A在全局范围中创建合并的命名空间:

在此输入图像描述

此设置很有用,但不适用于模块(因为模块不会污染全局范围).


版本3:无精打采

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

Mod1.ts

export class Twix { ... }
Run Code Online (Sandbox Code Playgroud)

Mod2.ts

export class PeanutButterCup { ... }
Run Code Online (Sandbox Code Playgroud)

Mod3.ts

export class KitKat { ... }
Run Code Online (Sandbox Code Playgroud)

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

在此输入图像描述

好多了!

现在,如果你还在考虑你真的想在模块中使用命名空间,请继续阅读......


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

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

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

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

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

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

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


即使您不相信模块的工作原理能够充分解决这些原因,尝试在外部模块中使用命名空间的"解决方案"也无法工作.

盒子里的盒子

一个故事:

你的朋友鲍勃打电话给你."我家里有一个很棒的新组织计划",他说,"来看看吧!".干得好,让我们看看Bob提出了什么.

你从厨房开始,打开食品室.有60个不同的盒子,每个盒子都标有"Pantry".你随机挑选一个盒子然后打开它.里面是一个标有"谷物"的盒子.打开"谷物"框,找到一个标有"Pasta"的方框.打开"Pasta"框,找到一个标有"Penne"的方框.你打开这个盒子,就像你期望的那样找到一袋通心粉.

有点困惑,你拿起一个相邻的盒子,也标有"Pantry".里面是一个盒子,再次标记为"谷物".打开"谷物"框,再次找到一个标有"Pasta"的方框.你打开"Pasta"框并找到一个盒子,这个盒子标有"Rigatoni".你打开这个盒子,找到一袋通心粉意大利面.

"这很棒!" 鲍勃说."一切都在命名空间!".

"但鲍勃......"你回答."你的组织方案是无用的.你必须打开一堆盒子才能找到任何东西,找到任何东西实际上并不比你把所有东西放在一个盒子而不是三个盒子里更方便.事实上,既然你的食品储藏室已经逐个分类,你根本不需要盒子.为什么不把面食放在架子上并在需要时拿起它?"

"你不明白 - 我需要确保没有其他人放置不属于'Pantry'命名空间的东西.而且我已经安全地将所有意大利面组织到Pantry.Grains.Pasta命名空间中,这样我就可以轻松找到它"

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

模块是他们自己的盒子

你可能在现实生活中遇到了类似的事情:你在亚马逊上订购了一些东西,每件物品都出现在自己的盒子里,里面有一个小盒子,你的物品用自己的包装包装.即使内部箱子相似,货物也无法"合并".

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


外部模块指南

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

导出尽可能接近顶级

  • 如果您只导出单个类或函数,请使用export default:

MyClass.ts

export default class SomeType {
  constructor() { ... }
}
Run Code Online (Sandbox Code Playgroud)

MyFunc.ts

function getThing() { return 'thing'; }
export default getThing;
Run Code Online (Sandbox Code Playgroud)

消费

import t from './MyClass';
import f from './MyFunc';
var x = new t();
console.log(f());
Run Code Online (Sandbox Code Playgroud)

这对消费者来说是最佳的.他们可以根据需要为您的类型命名(t在这种情况下),并且不必进行任何无关的点击来查找对象.

  • 如果您要导出多个对象,请将它们全部放在顶层:

MyThings.ts

export class SomeType { ... }
export function someFunc() { ... }
Run Code Online (Sandbox Code Playgroud)

消费

import * as m from './MyThings';
var x = new m.SomeType();
var y = m.someFunc();
Run Code Online (Sandbox Code Playgroud)
  • 如果您要导出大量内容,那么您应该使用module/ namespacekeyword:

MyLargeModule.ts

export namespace Animals {
  export class Dog { ... }
  export class Cat { ... }
}
export namespace Plants {
  export class Tree { ... }
}
Run Code Online (Sandbox Code Playgroud)

消费

import { Animals, Plants} from './MyLargeModule';
var x = new Animals.Dog();
Run Code Online (Sandbox Code Playgroud)

红旗

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

  • 一个只有顶级声明的文件export module Foo { ... }(删除Foo所有内容并将其移动到某个级别)
  • 具有单个export classexport function不具有的文件export default
  • export module Foo {在顶级具有相同的多个文件(不要认为这些文件将组合成一个Foo!)

  • 这是一个非答案.您不应该或不需要外部模块的命名空间的前提是错误的.虽然文件系统是一种组织方案,您可以_kinda_用于这些目的,但对于消费者而言,使用给定项目中的n个类或函数的n个import语句并不是那么好; 特别是因为当你在实际代码中失败时,它也会混淆命名约定. (69认同)
  • 我不明白,我们不再写pascal了.从什么时候组织使用文件系统的方式去? (22认同)
  • 无论人们多么想要它,它仍然是*不可能*. (12认同)
  • 很好的写作,谢谢.我觉得你应该从www.typescriptlang.org/docs/handbook/namespaces.html链接到这个.我必须读过typescriptlang.org链接3或4次,作为C#dev,我自然希望将所有内容都放在命名空间中.我已经阅读了一些建议,但没有解释为什么,也没有任何解释(并且很好地描述).打字稿文档中没有任何内容提到这个AFAIK (12认同)
  • 您可以通过"包装"模块导入和重新导出您图书馆消费者感兴趣的所有内容.但同样,使用"命名空间"除了强制使用代码的任何人的另一级间接之外,不会提供任何其他值. (8认同)
  • 因此,使用TypeScript模块,没有办法将我写的所有代码包装到一个名称空间中,比如我的名字或公司名称,而没有一个包含它的巨型模块.那是对的吗? (7认同)
  • 回答不完整; 没有代码吃糖果 (5认同)
  • @RyanCavanaugh 如果你想编写你的 ts 以便每个文件有一个类怎么办?在这种情况下,为了方便起见,我想将每个类文件导入到模块文件中。 (2认同)
  • 有时,如果我们想将实现拆分到不同的包中,我们就需要共享外部命名空间。例如,在 C# 中,“System.Collection”不需要位于一个包中,某些集合可以在多个包中实现,就像类未密封一样。拥有公司级命名空间可以减少 Windows 全局命名空间中的命名空间冲突。并且还为在多个包中实现可共享的动态组件提供了更大的灵活性 (2认同)
  • 不要使用导出默认值。您最终会得到文件/导入/导出语句的不同名称。相反,使用与导出变量相同的名称命名模块,并使用大括号将其导入另一个文件中。有事件 [lints](https://palantir.github.io/tslint/rules/no-default-export/) (2认同)

Jef*_*pia 53

Ryan的答案没有错,但是对于那些来到这里寻找如何在仍然正确使用ES6命名空间的同时维护每个文件一个类文件结构的人来说,请参考Microsoft 提供的这个有用的资源.

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

编辑 Circling返回以更新此答案.在TS中出现了一些命名空间的方法.

一个文件中的所有模块类.

export namespace Shapes {
    export class Triangle {}
    export class Square {}      
}
Run Code Online (Sandbox Code Playgroud)

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

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

export namespace Shapes {
  export const Triangle = _Triangle;
  export const Square = _Square;
}
Run Code Online (Sandbox Code Playgroud)

// ./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();
Run Code Online (Sandbox Code Playgroud)

最后的考虑.您可以命名每个文件

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

// square.ts
export namespace Shapes {
    export class Square {}
}
Run Code Online (Sandbox Code Playgroud)

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

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

// ugh
let myTriangle = new _Shapes.Shapes.Triangle();
Run Code Online (Sandbox Code Playgroud)

这种混叠是绝对令人憎恶的,所以不要这样做.你最好采用上述方法.就个人而言,我更喜欢'桶'.

  • 什么是"ES6命名空间"? (6认同)

Alb*_*chy 7

尝试按文件夹组织:

baseTypes.ts

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

export class Plant {
    photosynthesize() { /* ... */ }
}
Run Code Online (Sandbox Code Playgroud)

dog.ts

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

export class Dog extends b.Animal {
    woof() { }
}   
Run Code Online (Sandbox Code Playgroud)

tree.ts

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

class Tree extends b.Plant {
}
Run Code Online (Sandbox Code Playgroud)

LivingThings.ts

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

export = {
    dog: dog,
    tree: tree
}
Run Code Online (Sandbox Code Playgroud)

main.ts

import LivingThings = require('./LivingThings');
console.log(LivingThings.Tree)
console.log(LivingThings.Dog)
Run Code Online (Sandbox Code Playgroud)

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

  • LivingThings.dog.Dog就在这里. (8认同)

小智 5

尝试这个命名空间模块

命名空间模块文件.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);
    }
}
}
Run Code Online (Sandbox Code Playgroud)

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');
Run Code Online (Sandbox Code Playgroud)