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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/30357634/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 05:04:03  来源:igfitidea点击:

How do I use namespaces with TypeScript external modules?

javascriptmoduletypescript

提问by Ryan Cavanaugh

I have some code:

我有一些代码:

baseTypes.ts

基本类型.ts

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

dog.ts

狗.ts

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

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

tree.ts

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

  }
}

This is all very confusing. I want to have a bunch of external modules all contribute types to the same namespace, Living.Things. It seems that this doesn't work at all -- I can't see Animalin dogs.ts. I have to write the full namespace name b.Living.Things.Plantin tree.ts. It doesn't work to combine multiple objects in the same namespace across file. How do I do this?

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

回答by Ryan Cavanaugh

Candy Cup Analogy

糖果杯比喻

Version 1: A cup for every candy

版本 1:每个糖果一个杯子

Let's say you wrote some code like this:

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

Mod1.ts

Mod1.ts

export namespace A {
    export class Twix { ... }
}

Mod2.ts

Mod2.ts

export namespace A {
    export class PeanutButterCup { ... }
}

Mod3.ts

Mod3.ts

export namespace A {
     export class KitKat { ... }
}

You've created this setup: enter image description here

您已创建此设置: 在此处输入图片说明

Each module (sheet of paper) gets its own cupnamed A. This is useless - you're not actually organizingyour candy here, you're just adding an additional step (taking it out of the cup) between you and the treats.

每个模块(一张纸)都有自己的杯子,名为A. 这是没用的 - 你实际上并没有在这里整理你的糖果,你只是在你和零食之间增加了一个额外的步骤(把它从杯子里拿出来)。



Version 2: One cup in the global scope

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

If you weren't using modules, you might write code like this (note the lack of exportdeclarations):

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

global1.ts

global1.ts

namespace A {
    export class Twix { ... }
}

global2.ts

global2.ts

namespace A {
    export class PeanutButterCup { ... }
}

global3.ts

global3.ts

namespace A {
     export class KitKat { ... }
}

Thiscode creates a merged namespace Ain the global scope:

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

enter image description here

在此处输入图片说明

This setup is useful, but doesn't apply in the case of modules (because modules don't pollute the global scope).

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



Version 3: Going cupless

版本 3:无杯

Going back to the original example, the cups A, A, and Aaren't doing you any favors. Instead, you could write the code as:

回到最初的例子,杯子AA、 和A对你没有任何帮助。相反,您可以将代码编写为:

Mod1.ts

Mod1.ts

export class Twix { ... }

Mod2.ts

Mod2.ts

export class PeanutButterCup { ... }

Mod3.ts

Mod3.ts

export class KitKat { ... }

to create a picture that looks like this:

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

enter image description here

在此处输入图片说明

Much better!

好多了!

Now, if you're still thinking about how much you really want to use namespace with your modules, read on...

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



These Aren't the Concepts You're Looking For

这些不是您要寻找的概念

We need to go back to the origins of why namespaces exist in the first place and examine whether those reasons make sense for external modules.

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

Organization: Namespaces are handy for grouping together logically-related objects and types. For example, in C#, you're going to find all the collection types in System.Collections. By organizing our types into hierarchical namespaces, we provide a good "discovery" experience for users of those types.

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

Name Conflicts: Namespaces are important to avoid naming collisions. For example, you might have My.Application.Customer.AddFormand My.Application.Order.AddForm-- two types with the same name, but a different namespace. In a language where all identifiers exist in the same root scope and all assemblies load all types, it's critical to have everything be in a namespace.

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

Do those reasons make sense in external modules?

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

Organization: External modules are already present in a file system, necessarily. We have to resolve them by path and filename, so there's a logical organization scheme for us to use. We can have a /collections/generic/folder with a listmodule in it.

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

Name Conflicts: This doesn't apply at all in external modules. Withina module, there's no plausible reason to have two objects with the same name. From the consumption side, the consumerof any given module gets to pick the name that they will use to refer to the module, so accidental naming conflicts are impossible.

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



Even if you don't believe that those reasons are adequately addressed by how modules work, the "solution" of trying to use namespaces in external modules doesn't even work.

即使您不相信模块的工作方式可以充分解决这些原因,尝试在外部模块中使用命名空间的“解决方案”甚至不起作用。

Boxes in Boxes in Boxes

盒中盒盒中盒

A story:

一个故事:

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 onebox 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.Pastanamespace so I can easily find it"

Bob is a very confused man.

你的朋友鲍勃给你打电话。“我家里有一个很棒的新组织计划”,他说,“快来看看吧!”。好吧,让我们去看看鲍勃想出了什么。

你从厨房开始,打开储藏室。有 60 个不同的盒子,每个盒子都标有“Pantry”。你随机挑选一个盒子并打开它。里面是一个标有“谷物”的盒子。你打开“谷物”盒子,找到一个标有“意大利面”的盒子。你打开“Pasta”盒子,找到一个标有“Penne”的盒子。你打开这个盒子,如你所料,发现了一袋通心粉。

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

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

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

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

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

Modules are Their Own Box

模块是他们自己的盒子

You've probably had something similar happen in real life: You order a few things on Amazon, and each item shows up in its own box, with a smaller box inside, with your item wrapped in its own packaging. Even if the interior boxes are similar, the shipments are not usefully "combined".

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

Going with the box analogy, the key observation is that external modules are their own box. It might be a very complex item with lots of functionality, but any given external module is its own box.

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



Guidance for External Modules

外部模块指南

Now that we've figured out that we don't need to use 'namespaces', how should we organize our modules? Some guiding principles and examples follow.

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

Export as close to top-level as possible

尽可能接近顶级导出

  • If you're only exporting a single class or function, use export default:
  • 如果您只导出单个类或函数,请使用export default

MyClass.ts

MyClass.ts

export default class SomeType {
  constructor() { ... }
}

MyFunc.ts

MyFunc.ts

function getThing() { return 'thing'; }
export default getThing;

Consumption

消耗

import t from './MyClass';
import f from './MyFunc';
var x = new t();
console.log(f());

This is optimal for consumers. They can name your type whatever they want (tin this case) and don't have to do any extraneous dotting to find your objects.

这对消费者来说是最佳的。他们可以随意命名您的类型(t在这种情况下),并且无需进行任何无关的打点即可找到您的对象。

  • If you're exporting multiple objects, put them all at top-level:
  • 如果您要导出多个对象,请将它们全部放在顶层:

MyThings.ts

我的东西.ts

export class SomeType { ... }
export function someFunc() { ... }

Consumption

消耗

import * as m from './MyThings';
var x = new m.SomeType();
var y = m.someFunc();
  • If you're exporting a large number of things, only then should you use the module/namespacekeyword:
  • 如果您要导出大量内容,那么您才应该使用module/namespace关键字:

MyLargeModule.ts

MyLargeModule.ts

export namespace Animals {
  export class Dog { ... }
  export class Cat { ... }
}
export namespace Plants {
  export class Tree { ... }
}

Consumption

消耗

import { Animals, Plants} from './MyLargeModule';
var x = new Animals.Dog();


Red Flags

红旗

All of the following are red flags for module structuring. Double-check that you're not trying to namespace your external modules if any of these apply to your files:

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

  • A file whose only top-level declaration is export module Foo { ... }(remove Fooand move everything 'up' a level)
  • A file that has a single export classor export functionthat isn't export default
  • Multiple files that have the same export module Foo {at top-level (don't think that these are going to combine into one Foo!)
  • 一个文件,其唯一的顶级声明是export module Foo { ... }(删除Foo并将所有内容“上移”一个级别)
  • 一个文件有一个export classexport function没有export default
  • export module Foo {在顶层具有相同的多个文件(不要认为这些文件会合并为一个Foo!)

回答by Jefftopia

Nothing wrong with Ryan's answer, but for people who came here looking for how to maintain a one-class-per-filestructure while still using ES6 namespaces correctly please refer to thishelpful resource from Microsoft.

Ryan 的回答没有任何问题,但是对于来到这里寻找如何在仍然正确使用 ES6 命名空间的同时维护一个类每个文件结构的人,请参阅来自 Microsoft 的这个有用资源。

One thing that's unclear to me after reading the doc is: how to import the entire (merged) module with a singleimport.

阅读文档后,我不清楚的一件事是:如何使用单个import.

EditCircling back to update this answer. A few approaches to namespacing emerge in TS.

编辑回圈以更新此答案。TS 中出现了一些命名空间的方法。

All module classes in one file.

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

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

Import files into namespace, and reassign

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

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

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

Barrels

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

A final consideration. You couldnamespace each file

最后的考虑。你可以命名每个文件

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

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

But as one imports two classes from the same namespace, TS will complain there's a duplicate identifier. The only solution as this time is to then alias the namespace.

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

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

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

This aliasing is absolutely abhorrent, so don't do it. You're better off with an approach above. Personally, I prefer the 'barrel'.

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

回答by Albinofrenchy

Try to organize by folder:

尝试按文件夹组织:

baseTypes.ts

基本类型.ts

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

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

dog.ts

狗.ts

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

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

tree.ts

树.ts

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

class Tree extends b.Plant {
}

LivingThings.ts

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)

The idea is that your module themselves shouldn't care / know they are participating in a namespace, but this exposes your API to the consumer in a compact, sensible way which is agnostic to which type of module system you are using for the project.

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

回答by Mike Vitik

Small impovement of Albinofrenchy answer:

白化法答案的小改进:

base.ts

base.ts

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

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

dog.ts

狗.ts

import * as b from './base';

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

things.ts

东西.ts

import { Dog } from './dog'

namespace things {
  export const dog = Dog;
}

export = things;

main.ts

主文件

import * as things from './things';

console.log(things.dog);

回答by NO... Bugs...

OP I'm with you man. again too, there is nothing wrong with that answer with 300+ up votes, but my opinion is:

OP我和你在一起。同样,300+票的答案没有任何问题,但我的意见是:

  1. what is wrong with putting classes into their cozy warm own files individually? I mean this will make things looks much better right? (or someone just like a 1000 line file for all the models)

  2. so then, if the first one will be achieved, we have to import import import... import just in each of the model files like man, srsly, a model file, a .d.ts file, why there are so many *s in there? it should just be simple, tidy, and that's it. Why I need imports there? why? C# got namespaces for a reason.

  3. And by then, you are literally using "filenames.ts" as identifiers. As identifiers... Come on its 2017 now and we still do that? Ima go back to Mars and sleep for another 1000 years.

  1. 将课程单独放入他们舒适温暖的自己的文件中有什么问题?我的意思是这会让事情看起来好多了,对吧?(或者有人喜欢所有模型的 1000 行文件)

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

  3. 到那时,您实际上是在使用“filenames.ts”作为标识符。作为标识符......现在是 2017 年,我们仍然这样做吗?我回到火星再睡1000年。

So sadly, my answer is: nop, you cannot make the "namespace" thing functional if you do not using all those imports or using those filenames as identifiers (which I think is really silly). Another option is: put all of those dependencies into a box called filenameasidentifier.ts and use

很遗憾,我的回答是:不,如果您不使用所有这些导入或使用这些文件名作为标识符(我认为这真的很愚蠢),则无法使“命名空间”功能起作用。另一种选择是:将所有这些依赖项放入一个名为 filenameasidentifier.ts 的框中并使用

export namespace(or module) boxInBox {} .

wrap them so they wont try to access other classes with same name when they are just simply trying to get a reference from the class sit right on top of them.

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

回答by Ryan Thomas

Several of the questions/comments I've seen around this subject sound to me as if the person is using Namespacewhere they mean 'module alias'. As Ryan Cavanaugh mentioned in one of his comments you can have a 'Wrapper' module re-export several modules.

我在这个主题周围看到的几个问题/评论对我来说听起来好像这个人正在使用Namespace他们的意思是“模块别名”。正如 Ryan Cavanaugh 在他的评论之一中提到的,您可以让“包装器”模块重新导出多个模块。

If you really want to import it all from the same module name/alias, combine a wrapper module with a paths mapping in your tsconfig.json.

如果您真的想从相同的模块名称/别名中导入它,请将包装器模块与tsconfig.json.

Example:

例子:

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

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

export class Foo {
    ...
}


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


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

export class Bar {
    ...
}


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


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

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



tsconfig.json



tsconfig.json

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



main.ts



main.ts

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

Note: The module resolution in the output .js files will need to be handled somehow, such as with this https://github.com/tleunen/babel-plugin-module-resolver

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

Example .babelrcto handle the alias resolution:

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

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

回答by Bal mukund kumar

Try this namespaces module

试试这个命名空间模块

namespaceModuleFile.ts

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

bookTreeCombine.ts

---compilation part---

---编译部分---

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

回答by Alessandro Lendaro

dog.ts

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

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

回答by NolePTR

The proper way to organize your code is to use separate directories in place of namespaces. Each class will be in it's own file, in it's respective namespace folder. index.ts will only re-export each file; no actual code should be in the index.ts file. Organizing your code like this makes it far easier to navigate, and is self-documenting based on directory structure.

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

You would then use it as such:

然后你可以这样使用它:

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