Wie verwende ich Namespaces mit externen TypeScript-Modulen?

232

Ich habe einen Code:

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 {

  }
}

Das ist alles sehr verwirrend. Ich möchte eine Reihe von externen Modulen haben, die alle Typen zum gleichen Namespace beitragen Living.Things. Es scheint , dass dies nicht funktioniert überhaupt - ich kann nicht sehen , Animalin dogs.ts. Ich habe die vollständigen Namespace Namen schreiben b.Living.Things.Plantin tree.ts. Es funktioniert nicht, mehrere Objekte im selben Namespace in einer Datei zu kombinieren. Wie mache ich das?

Ryan Cavanaugh
quelle

Antworten:

858

Candy Cup Analogie

Version 1: Eine Tasse für jede Süßigkeit

Angenommen, Sie haben einen Code wie diesen geschrieben:

Mod1.ts

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

Mod2.ts

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

Mod3.ts

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

Sie haben dieses Setup erstellt: Geben Sie hier die Bildbeschreibung ein

Jedes Modul (Blatt Papier) erhält eine eigene Tasse mit dem Namen A. Dies ist nutzlos - Sie organisieren Ihre Süßigkeiten hier nicht wirklich , sondern fügen lediglich einen zusätzlichen Schritt (Herausnehmen aus der Tasse) zwischen Ihnen und den Leckereien hinzu.


Version 2: Eine Tasse im globalen Bereich

Wenn Sie keine Module verwenden, können Sie Code wie diesen schreiben (beachten Sie das Fehlen von exportDeklarationen):

global1.ts

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

global2.ts

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

global3.ts

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

Dieser Code erstellt einen zusammengeführten Namespace Aim globalen Bereich:

Geben Sie hier die Bildbeschreibung ein

Dieses Setup ist nützlich, gilt jedoch nicht für Module (da Module den globalen Bereich nicht verschmutzen).


Version 3: Cupless gehen

Gehen wir zurück zum ursprünglichen Beispiel, die Becher A, Aund Atun Sie keinen Gefallen. Stattdessen können Sie den Code wie folgt schreiben:

Mod1.ts

export class Twix { ... }

Mod2.ts

export class PeanutButterCup { ... }

Mod3.ts

export class KitKat { ... }

um ein Bild zu erstellen, das so aussieht:

Geben Sie hier die Bildbeschreibung ein

Viel besser!

Wenn Sie immer noch darüber nachdenken, wie oft Sie den Namespace wirklich mit Ihren Modulen verwenden möchten, lesen Sie weiter ...


Dies sind nicht die Konzepte, nach denen Sie suchen

Wir müssen zu den Ursprüngen zurückkehren, warum Namespaces überhaupt existieren, und untersuchen, ob diese Gründe für externe Module sinnvoll sind.

Organisation : Namespaces sind praktisch, um logisch verwandte Objekte und Typen zu gruppieren. In C # finden Sie beispielsweise alle Sammlungstypen in System.Collections. Durch die Organisation unserer Typen in hierarchischen Namespaces bieten wir Benutzern dieser Typen eine gute "Discovery" -Erfahrung.

Namenskonflikte : Namensräume sind wichtig , um zu vermeiden Namenskollisionen. Beispielsweise könnten Sie My.Application.Customer.AddFormund My.Application.Order.AddForm- zwei Typen mit demselben Namen, aber einem anderen Namespace haben. In einer Sprache, in der alle Bezeichner im selben Stammbereich vorhanden sind und alle Assemblys alle Typen laden, ist es wichtig, dass sich alles in einem Namespace befindet.

Sind diese Gründe in externen Modulen sinnvoll?

Organisation : Externe Module sind notwendigerweise bereits in einem Dateisystem vorhanden. Wir müssen sie nach Pfad und Dateiname auflösen, damit wir ein logisches Organisationsschema verwenden können. Wir können einen /collections/generic/Ordner mit einem listModul darin haben.

Namenskonflikte : Dies gilt überhaupt nicht für externe Module. Innerhalb eines Moduls gibt es keinen plausiblen Grund, zwei Objekte mit demselben Namen zu haben. Auf der Verbraucherseite kann der Verbraucher eines bestimmten Moduls den Namen auswählen, mit dem er auf das Modul verweist, sodass versehentliche Namenskonflikte nicht möglich sind.


Selbst wenn Sie nicht glauben, dass diese Gründe durch die Funktionsweise von Modulen angemessen berücksichtigt werden, funktioniert die "Lösung" des Versuchs, Namespaces in externen Modulen zu verwenden, nicht einmal.

Boxen in Boxen in Boxen

Eine Geschichte:

Dein Freund Bob ruft dich an. "Ich habe ein großartiges neues Organisationsschema in meinem Haus", sagt er, "komm und schau es dir an!". Ordentlich, lass uns sehen, was Bob sich ausgedacht hat.

Sie beginnen in der Küche und öffnen die Speisekammer. Es gibt 60 verschiedene Boxen mit der Aufschrift "Pantry". Sie wählen eine Box nach dem Zufallsprinzip aus und öffnen sie. Im Inneren befindet sich eine einzelne Box mit der Aufschrift "Grains". Sie öffnen das Feld "Getreide" und finden ein einzelnes Feld mit der Bezeichnung "Pasta". Sie öffnen die Box "Pasta" und finden eine einzelne Box mit der Bezeichnung "Penne". Sie öffnen diese Schachtel und finden erwartungsgemäß eine Tüte Penne-Nudeln.

Etwas verwirrt nehmen Sie eine benachbarte Kiste mit der Aufschrift "Pantry". Im Inneren befindet sich eine einzelne Schachtel mit der Aufschrift "Grains". Sie öffnen das Feld "Getreide" und finden erneut ein einzelnes Feld mit der Bezeichnung "Pasta". Sie öffnen die Box "Pasta" und finden eine einzelne Box mit der Bezeichnung "Rigatoni". Sie öffnen diese Schachtel und finden ... eine Tüte Rigatoni-Nudeln.

"Es ist toll!" sagt Bob. "Alles ist in einem Namespace!".

"Aber Bob ..." antwortest du. „Ihr Organisationsschema ist nutzlos. Sie haben ein paar Kisten zu öffnen, um etwas zu bekommen, und es ist eigentlich nicht mehr praktisch alles als zu finden , wenn Sie hatte gerade alles setzen in einer Box statt drei . In der Tat, da Ihr Die Speisekammer ist bereits Regal für Regal sortiert, Sie brauchen die Kisten überhaupt nicht. Warum stellen Sie die Nudeln nicht einfach auf das Regal und holen sie ab, wenn Sie sie brauchen? "

"Du verstehst nicht - ich muss sicherstellen, dass niemand anderes etwas in den 'Pantry'-Namespace einfügt. Und ich habe alle meine Nudeln sicher im Pantry.Grains.PastaNamespace organisiert, damit ich es leicht finden kann."

Bob ist ein sehr verwirrter Mann.

Module sind ihre eigene Box

Wahrscheinlich ist im wirklichen Leben etwas Ähnliches passiert: Sie bestellen ein paar Dinge bei Amazon, und jeder Artikel wird in einer eigenen Schachtel mit einer kleineren Schachtel angezeigt, wobei Ihr Artikel in einer eigenen Verpackung verpackt ist. Selbst wenn die Innenboxen ähnlich sind, werden die Sendungen nicht sinnvoll "kombiniert".

In Übereinstimmung mit der Box-Analogie ist die wichtigste Beobachtung, dass externe Module ihre eigene Box sind . Es mag ein sehr komplexes Element mit vielen Funktionen sein, aber jedes externe Modul ist eine eigene Box.


Anleitung für externe Module

Nachdem wir herausgefunden haben, dass wir keine 'Namespaces' verwenden müssen, wie sollen wir unsere Module organisieren? Es folgen einige Leitprinzipien und Beispiele.

Exportieren Sie so nah wie möglich an die oberste Ebene

  • Wenn Sie nur eine einzelne Klasse oder Funktion exportieren, verwenden Sie export default:

MyClass.ts

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

MyFunc.ts

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

Verbrauch

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

Dies ist optimal für die Verbraucher. Sie können Ihren Typ benennen, was immer sie wollen ( tin diesem Fall) und müssen keine zusätzlichen Punkte machen, um Ihre Objekte zu finden.

  • Wenn Sie mehrere Objekte exportieren, platzieren Sie sie alle auf der obersten Ebene:

MyThings.ts

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

Verbrauch

import * as m from './MyThings';
var x = new m.SomeType();
var y = m.someFunc();
  • Wenn Sie eine große Anzahl von Dingen exportieren, sollten Sie nur dann das Schlüsselwort module/ verwenden namespace:

MyLargeModule.ts

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

Verbrauch

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

Rote Flaggen

Alle folgenden sind rote Fahnen für die Modulstrukturierung. Stellen Sie sicher, dass Sie nicht versuchen, Ihre externen Module zu benennen, wenn eines dieser Elemente für Ihre Dateien gilt:

  • Eine Datei, deren einzige Deklaration der obersten Ebene lautet export module Foo { ... }( Fooalles entfernen und auf einer Ebene nach oben verschieben)
  • Eine Datei, die eine Single hat export classoder export functionnichtexport default
  • Mehrere Dateien, die export module Foo {auf oberster Ebene dasselbe haben (denken Sie nicht, dass diese zu einer kombiniert werden Foo!)
Ryan Cavanaugh
quelle
80
Dies ist keine Antwort. Die Prämisse, dass Sie keine Namespaces für externe Module benötigen oder wollen sollten, ist fehlerhaft. Während das Dateisystem eine Art Organisationsschema ist, das Sie für diese Zwecke verwenden können, ist es für den Verbraucher bei weitem nicht so schön, n Importanweisungen für die Verwendung von n Klassen oder Funktionen aus einem bestimmten Projekt zu haben. zumal es auch die Namenskonvention trübt, wenn Sie sich im eigentlichen Code befinden.
Albinofrenchy
12
Egal wie sehr man es möchte, es ist immer noch nicht möglich .
Ryan Cavanaugh
26
Ich verstehe nicht, wir schreiben nicht mehr Pascal. Seit wann ist das Organisieren mit dem Dateisystem der richtige Weg?
David
9
Sie können ein "Wrapper" -Modul verwenden, das alles, was für die Verbraucher Ihrer Bibliothek von Interesse ist, importiert und erneut exportiert. Aber auch hier wird die Verwendung eines "Namespace" keinen anderen Wert liefern, als eine andere Indirektionsebene für jemanden zu erzwingen, der Ihren Code verwendet.
Ryan Cavanaugh
13
Tolles Schreiben, danke. Ich denke, Sie sollten von www.typescriptlang.org/docs/handbook/namespaces.html darauf verlinken. Ich muss diesen TypScriptlang.org-Link drei- oder viermal gelesen haben und als C # -Entwickler möchte ich natürlich alles in einen Namespace stellen. Ich habe einige Vorschläge gelesen, die nicht dazu sagen, aber ohne Erklärung warum und nichts so endgültiges (und gut beschriebenes) wie dieses. Plus nichts in den Typoskriptdokumenten erwähnt diese AFAIK
Adam Plocher
53

An Ryans Antwort ist nichts auszusetzen, aber für Leute, die hierher gekommen sind, um eine Struktur für eine Klasse pro Datei beizubehalten , während sie ES6-Namespaces weiterhin korrekt verwenden, lesen Sie bitte diese hilfreiche Ressource von Microsoft.

Eine Sache, die mir nach dem Lesen des Dokuments unklar ist, ist: wie man das gesamte (zusammengeführte) Modul mit einem einzigen importiert import.

Bearbeiten Sie das Zurückkreisen, um diese Antwort zu aktualisieren. In TS gibt es einige Ansätze für den Namespace.

Alle Modulklassen in einer Datei.

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

Importieren Sie Dateien in den Namespace und weisen Sie sie neu zu

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

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

Fässer

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

Eine letzte Überlegung. Sie können jede Datei mit einem Namespace versehen

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

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

Da man jedoch zwei Klassen aus demselben Namespace importiert, beschwert sich TS, dass es eine doppelte Kennung gibt. Die einzige Lösung besteht diesmal darin, den Namespace als Alias ​​zu verwenden.

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

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

Dieses Aliasing ist absolut abscheulich, also tu es nicht. Mit dem obigen Ansatz sind Sie besser dran. Persönlich bevorzuge ich das "Fass".

Jefftopia
quelle
6
Was sind "ES6-Namespaces"?
Aluan Haddad
@AluanHaddad Beim Importieren von es2015 + sind die importierten Elemente entweder standardmäßig, destrukturiert oder im Namespace. const fs = require('fs'), fsIst der Namespace. import * as moment from 'moment', momentIst der Namespace. Dies ist Ontologie, nicht die Spezifikation.
Jefftopia
Ich bin mir dessen bewusst, aber Sie sollten es in Ihrer Antwort gut erklären. ES6-Namespaces sind jedoch tatsächlich eine Sache, und das requireBeispiel gilt aus einer Reihe von Gründen nicht für sie, einschließlich der Tatsache, dass ES6-Namespaces möglicherweise nicht aufgerufen werden, während requireein einfaches Objekt zurückgegeben wird, das möglicherweise aufgerufen werden kann.
Aluan Haddad
1
Ich folge nicht, denn ob das importierte Ding aufrufbar ist oder nicht, es dient logischerweise immer noch als Namespace . Ich denke nicht, dass die Vorbehalte für meine obige Antwort wesentlich sind.
Jefftopia
7

Versuchen Sie, nach Ordner zu organisieren:

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)

Die Idee ist, dass Ihr Modul selbst sich nicht darum kümmern sollte / weiß, dass es an einem Namespace teilnimmt, aber dies stellt Ihre API dem Verbraucher auf kompakte, vernünftige Weise zur Verfügung, die unabhängig von der Art des Modulsystems ist, das Sie für das Projekt verwenden.

Albinofrenchy
quelle
8
LivingThings.dog.Dog ist das, was Sie hier haben.
Corey Alix
Ich empfehle, die Groß- und Kleinschreibung konsistent zu halten. Wenn Sie "Baum" exportieren, importieren Sie "Baum" und nicht "Baum".
Demisx
1
Wie können Sie auch etwas importieren, tree.tswenn es überhaupt kein exportiertes Mitglied hat?
Demisx
Man TS hat sicher eine dumme alte Syntax, wie importund requirezusammen in einer Aussage.
Andy
3

Kleine Verbesserung von Albinofrenchy Antwort:

base.ts

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

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

dog.ts

import * as b from './base';

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

Dinge.ts

import { Dog } from './dog'

namespace things {
  export const dog = Dog;
}

export = things;

main.ts

import * as things from './things';

console.log(things.dog);
Mike Vitik
quelle
2
Danke dafür! Ich wollte nur sagen, dass Änderungen an einer vorhandenen Antwort vorzugsweise nicht als neue Antworten veröffentlicht werden sollten: Sie sollten entweder als Kommentar zur vorhandenen Antwort hinzugefügt oder (besser) vorgeschlagen werden, indem eine Änderung der gewünschten Antwort vorgeschlagen wird verbessern.
A3nm
3

OP Ich bin bei dir, Mann. Auch an dieser Antwort mit mehr als 300 Stimmen ist nichts auszusetzen, aber meine Meinung ist:

  1. Was ist falsch daran, Klassen einzeln in ihre gemütlichen, warmen eigenen Dateien zu legen? Ich meine, das wird die Dinge viel besser aussehen lassen, oder? (oder jemand wie eine 1000-Zeilen-Datei für alle Modelle)

  2. Wenn also die erste erreicht wird, müssen wir importieren importieren importieren ... nur in jede der Modelldateien importieren, wie man, srsly, eine Modelldatei, eine .d.ts-Datei, warum gibt es so viele * ist da drin? es sollte einfach, ordentlich und das war's. Warum brauche ich dort Importe? Warum? C # hat aus einem bestimmten Grund Namespaces.

  3. Und bis dahin verwenden Sie buchstäblich "filenames.ts" als Bezeichner. Als Identifikatoren ... Kommst du jetzt auf 2017 und machen wir das immer noch? Ich gehe zurück zum Mars und schlafe noch 1000 Jahre.

Meine Antwort lautet leider: Nein, Sie können das "Namespace" -Ding nicht funktionsfähig machen, wenn Sie nicht alle diese Importe oder Dateinamen als Bezeichner verwenden (was ich wirklich dumm finde). Eine weitere Option ist: Legen Sie alle diese Abhängigkeiten in ein Feld namens filenameasidentifier.ts und verwenden Sie

export namespace(or module) boxInBox {} .

Wickeln Sie sie so ein, dass sie nicht versuchen, auf andere Klassen mit demselben Namen zuzugreifen, wenn sie nur versuchen, eine Referenz von der Klasse zu erhalten, die direkt über ihnen sitzt.

NEIN ... Bugs ...
quelle
3

Einige der Fragen / Kommentare, die ich zu diesem Thema gesehen habe, klingen für mich so, als würde die Person dort verwenden, Namespacewo sie "Modulalias" bedeutet. Wie Ryan Cavanaugh in einem seiner Kommentare erwähnt hat, können Sie mit einem 'Wrapper'-Modul mehrere Module erneut exportieren.

Wenn Sie wirklich alles aus demselben Modulnamen / Alias ​​importieren möchten, kombinieren Sie ein Wrapper-Modul mit einer Pfadzuordnung in Ihrem tsconfig.json.

Beispiel:

./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'

Hinweis : Die Modulauflösung in den Ausgabe-.js-Dateien muss irgendwie gehandhabt werden, z. B. mit diesem https://github.com/tleunen/babel-plugin-module-resolver

Beispiel .babelrcfür die Behandlung der Aliasauflösung:

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

Probieren Sie dieses Namespaces-Modul aus

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

--- Zusammenstellungsteil ---

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');
Bal Mukund Kumar
quelle
0

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 {
    }
}
Alessandro Lendaro
quelle
-1

Die richtige Art, Ihren Code zu organisieren, besteht darin, anstelle von Namespaces separate Verzeichnisse zu verwenden. Jede Klasse befindet sich in einer eigenen Datei im entsprechenden Namespace-Ordner. index.ts exportiert nur jede Datei erneut. In der Datei index.ts sollte sich kein tatsächlicher Code befinden. Wenn Sie Ihren Code so organisieren, wird die Navigation erheblich vereinfacht und anhand der Verzeichnisstruktur selbstdokumentiert.

// 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";

Sie würden es dann als solches verwenden:

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);
NolePTR
quelle
Dies ist irreführend und absolut falsch. So funktionieren Namespaces nicht. Auch beantwortet es die ops-Frage nicht.
Andrew McLagan