繁体   English   中英

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

[英]How do I use namespaces with TypeScript external modules?

我有一些代码:

基本类型.ts

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

狗.ts

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

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

树.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.Living.Things.Planttree.ts 跨文件组合同一命名空间中的多个对象是行不通的。 我该怎么做呢?

糖果杯比喻

版本 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”。 你随机挑选一个盒子并打开它。 里面是一个标有“谷物”的盒子。 你打开“谷物”盒子,找到一个标有“意大利面”的盒子。 你打开“Pasta”盒子,找到一个标有“Penne”的盒子。 你打开这个盒子,如你所料,发现了一袋通心粉。

有点困惑,你拿起一个相邻的盒子,也标有“Pantry”。 里面是一个盒子,同样标有“谷物”。 你打开“谷物”盒子,再次找到一个标有“意大利面”的盒子。 你打开“Pasta”盒子,找到一个盒子,这个盒子标有“Rigatoni”。 你打开这个盒子,发现……一袋意大利通心粉。

“这很棒!” 鲍勃说。 “一切都在一个命名空间中!”。

“但是鲍勃……”你回答。 “你的组织方案没用。你必须打开一堆盒子才能找到任何东西,实际上找东西并不比把所有东西放在一个盒子里而不是三个盒子里更方便。事实上,因为你的储藏室已经逐个分类了,你根本不需要盒子。为什么不把意大利面放在架子上,需要的时候拿起来呢?”

“你不明白——我需要确保没有其他人把不属于‘Pantry’命名空间的东西放进去。而且我已经安全地将我所有的意大利面组织到了Pantry.Grains.Pasta命名空间中,所以我很容易找到”

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

模块是他们自己的盒子

你可能在现实生活中遇到过类似的事情:你在亚马逊上订购了一些东西,每件商品都出现在自己的盒子里,里面有一个较小的盒子,你的物品用自己的包装包裹。 即使内部盒子相似,货物也不会有效地“组合”。

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


外部模块指南

既然我们已经发现我们不需要使用“命名空间”,那么我们应该如何组织我们的模块呢? 以下是一些指导原则和示例。

尽可能接近顶级导出

  • 如果您只导出单个类或函数,请使用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 ),并且无需进行任何无关的打点即可找到您的对象。

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

我的东西.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 !)

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

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

尝试按文件夹组织:

基本类型.ts

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

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

狗.ts

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

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

树.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
}

主文件

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

这个想法是你的模块本身不应该关心/知道他们参与了一个命名空间,但这以一种紧凑、明智的方式向消费者公开你的 API,这与你用于项目的模块系统类型无关。

我在这个主题周围看到的几个问题/评论对我来说听起来好像这个人正在使用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 ...
    ]
}

白化法答案的小改进:

基地.ts

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

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

狗.ts

import * as b from './base';

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

东西.ts

import { Dog } from './dog'

namespace things {
  export const dog = Dog;
}

export = things;

主文件

import * as things from './things';

console.log(things.dog);

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

包装它们,这样当他们只是试图从位于它们之上的类中获取引用时,它们就不会尝试访问具有相同名称的其他类。

试试这个命名空间模块

命名空间模块文件.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');

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

树.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 {
    }
}

您可以使用* as wrapper_var语法使所有导入的方法都可以在wrapper_varwrapper_var

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM