我如何使用命名空间与TypeScript外部模块?

我有一些代码:

baseTypes.ts

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

dog.ts

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


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


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。我必须在tree.ts中写入完整的命名空间名称b.Living.Things.Plant。它不能跨文件在同一个名称空间中组合多个对象。我怎么做呢?

205420 次浏览

糖果杯类比

版本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 < / p >

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

更好的!

现在,如果您还在考虑在模块中使用多少命名空间,请继续阅读……


这些不是你要找的概念

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

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

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

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

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

名称冲突:这并不适用于所有的外部模块。是一个模块,没有合理的理由让两个对象具有相同的名称。从使用端来看,任何给定模块的消费者都可以选择用于引用该模块的名称,因此不可能发生意外的命名冲突。


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

盒子里的盒子里的盒子

一个故事:

你的朋友鲍勃打电话给你。“我家里有一个很棒的新组织方案,”他说,“快来看看!”太好了,我们去看看鲍勃有什么主意。

你从厨房开始,打开食品储藏室。这里有60个不同的盒子,每个盒子都标着“储藏室”。你随机选择一个盒子并打开它。里面有一个单独的盒子,标着“谷物”。你打开“谷物”;然后找到一个标有“意大利面”的盒子。你打开“意大利面”;然后找到一个标有“penne”字样的盒子。你打开这个盒子,发现如你所料,是一袋通心粉。

你有点困惑,拿起旁边的一个盒子,上面也标着“食品柜”。里面是一个单独的盒子,同样贴上了“谷物”的标签。你打开“谷物”;然后,再找一个标有“意大利面”的盒子。你打开“意大利面”;然后找到一个盒子,这个盒子的标签是“rigatoni”。你打开这个盒子,发现…一袋意大利通心粉。

“太好了!“鲍勃说。“所有东西都在一个命名空间中!”

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

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

鲍勃是个很糊涂的人。

模块是它们自己的盒子

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

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


外部模块指南

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

导出尽可能接近顶级

  • 如果只导出一个类或函数,请使用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并将所有内容“向上”一层)
  • 只有一个不是export defaultexport classexport function的文件
  • 在顶层具有相同export module Foo {的多个文件(不要认为这些文件会组合成一个Foo!)

尝试按文件夹组织:

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暴露给消费者,而不知道你正在为项目使用哪种类型的模块系统。

Ryan的回答没有错,但对于那些来这里寻找如何在正确使用ES6名称空间的同时维护one-class-per-file结构的人,请参考微软的有用资源。

在阅读文档后,我不清楚的一件事是:如何使用 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();

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

白化病小改善答案:

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

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 {
}
}
OP我支持你,伙计。 同样,在300+票的情况下,这个答案没有错,但我的观点是:

  1. 把类单独放到它们自己舒适温暖的文件中有什么错? 我的意思是这会让事情看起来更好,对吧?(或者有人就像所有模型的1000行文件)

  2. 那么,如果要实现第一个目标,我们就必须进口进口…只在每个模型文件中导入,比如man, srsly,一个模型文件,一个。d。Ts文件,为什么里面有这么多* ?它应该简单,整洁,就是这样。为什么这里需要导入?为什么?c#有名称空间是有原因的。

  3. 到那时,您实际上是在使用“文件名”。作为标识符。作为标识符……拜托,现在都2017年了,我们还这么做?我要回到火星,再睡1000年。

因此,遗憾的是,我的回答是:不,如果不使用所有这些导入或使用这些文件名作为标识符(我认为这真的很愚蠢),您就不能使“名称空间”东西起作用。另一种选择是:将所有这些依赖项放入一个名为filenameasidentifier的框中。t和用法

export namespace(or module) boxInBox {} .

包装它们,这样它们就不会试图访问具有相同名称的其他类,当它们只是简单地试图从它们上面的类获得引用时。

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

如果你真的想从相同的模块名/别名导入它,可以将包装器模块与tsconfig.json中的路径映射结合起来。

例子:

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

export class Foo {
...
}
< p > < br > ./path/to/CompanyName.Products/Bar.ts < / p >
export class Bar {
...
}
< p > < br > ./path/to/CompanyName.Products/index.ts < / p >
export { Foo } from './Foo';
export { Bar } from './Bar';
< p > < br > < br > tsconfig.json < / p >
{
"compilerOptions": {
...
paths: {
...
"CompanyName.Products": ["./path/to/CompanyName.Products/index"],
...
}
...
}
...
}
< p > < br > < br > main.ts < / p >
import { Foo, Bar } from 'CompanyName.Products'

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

示例.babelrc处理别名解析:

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

试试这个命名空间模块

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

组织代码的正确方法是使用单独的目录来代替名称空间。每个类都在它自己的文件中,在它各自的命名空间文件夹中。索引。Ts只会重新导出每个文件;索引中不应包含实际代码。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);

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

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