簡體   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