wake-up-neo.com

Wie verwende ich Namespaces mit externen TypeScript-Modulen?

Ich habe einen Code:

baseTypes.ts

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

dog.ts

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

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

module 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 zu demselben Namensraum, Living.Things, beitragen. Es scheint, dass dies überhaupt nicht funktioniert - ich sehe Animal nicht in dogs.ts. Ich muss den vollständigen Namensraumnamen b.Living.Things.Plant in tree.ts schreiben. Es funktioniert nicht, mehrere Objekte im gleichen Namespace über Dateien hinweg zu kombinieren. Wie mache ich das?

182
Ryan Cavanaugh

Candy Cup Analogie

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

Angenommen, Sie haben folgenden Code 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: enter image description here

Jedes Modul (Blatt Papier) erhält seine eigene TasseA. Das ist nutzlos - du bist nicht wirklich organisierst ​​deine Süßigkeiten hier, du fügst nur einen zusätzlichen Schritt (nimmst sie aus der Tasse) zwischen dir und den Leckereien hinzu.


Version 2: Eine Tasse im globalen Maßstab

Wenn Sie keine Module verwenden, schreiben Sie möglicherweise folgenden Code (beachten Sie das Fehlen von export -Deklarationen):

global1.ts

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

global2.ts

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

global3.ts

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

This Code erstellt einen zusammengeführten Namespace A im globalen Bereich:

enter image description here

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


Version 3: Ohne Probleme

Zurück zum ursprünglichen Beispiel: Die Tassen A, A und A tun Ihnen keinen Gefallen. Stattdessen könnten Sie den Code wie folgt schreiben:

Mod1.ts

export class Twix { ... }

Mod2.ts

export class PeanutButterCup { ... }

Mod3.ts

export class KitKat { ... }

so erstellen Sie ein Bild, das folgendermaßen aussieht:

enter image description here

Viel besser!

Wenn Sie noch immer darüber nachdenken, wie viel Namespace Sie wirklich für Ihre Module verwenden möchten, lesen Sie weiter ...


Dies sind nicht die Konzepte, die 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.

Organization: Namespaces sind praktisch, um logisch zusammengehörige Objekte und Typen zu gruppieren. In C # finden Sie beispielsweise alle Auflistungstypen in System.Collections. Indem wir unsere Typen in hierarchischen Namespaces organisieren, bieten wir Benutzern dieser Typen eine gute "Discovery" -Erfahrung.

Namenskonflikte: Namespaces sind wichtig, um Namenskollisionen zu vermeiden. Beispielsweise könnten Sie My.Application.Customer.AddForm Und My.Application.Order.AddForm Haben - zwei Typen mit dem gleichen Namen, aber einem anderen Namespace. In einer Sprache, in der alle Bezeichner im selben Stammbereich vorhanden sind und alle Assemblys alle Typen laden, muss sich alles in einem Namespace befinden.

Sind diese Gründe in externen Modulen sinnvoll?

Organisation: Externe Module sind in einem Dateisystem bereits 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 list Modul 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 keine versehentlichen Namenskonflikte auftreten können.


Auch wenn Sie nicht glauben, dass diese Gründe durch die Funktionsweise von Modulen angemessen angegangen werden, funktioniert die "Lösung" für den Versuch, Namespaces in externen Modulen zu verwenden, nicht.

Kästen in Kästen in Kästen

Eine Geschichte:

Dein Freund Bob ruft dich an. "Ich habe ein großartiges neues Organisationsschema in meinem Haus", sagt er. Lass uns sehen, was Bob sich ausgedacht hat.

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

Etwas verwirrt hebst du eine benachbarte Schachtel mit der Aufschrift "Pantry" auf. Im Inneren befindet sich eine einzelne Schachtel mit der Aufschrift "Körner". Sie öffnen das Feld "Getreide" und finden erneut ein einzelnes Feld mit der Bezeichnung "Pasta". Sie öffnen die "Pasta" -Box 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 Namensraum!".

"Aber Bob ..." antwortest du. "Ihr Organisationsschema ist nutzlos. Sie müssen eine Reihe von Kisten öffnen, um zu etwas zu gelangen, und es ist eigentlich nicht bequemer, etwas zu finden, als wenn Sie einfach alles in eine statt in eine Kiste gelegt hätten drei. Da Ihre Speisekammer bereits Regal für Regal sortiert ist, benötigen Sie die Kartons überhaupt nicht. Stellen Sie die Nudeln einfach auf das Regal und holen Sie sie ab, wenn Sie sie benötigen es?"

"Sie verstehen nicht - ich muss sicherstellen, dass niemand anderes etwas in den 'Pantry'-Namespace einfügt, das nicht dazugehört. Und ich habe alle meine Pasta sicher in den Namespace Pantry.Grains.Pasta Eingeordnet Ich kann es leicht finden "

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 darin angezeigt, wobei der Artikel in einer eigenen Verpackung verpackt ist. Auch wenn die Innenboxen ähnlich sind, werden die Sendungen nicht sinnvoll "kombiniert".

Entsprechend der Box-Analogie ist die wichtigste Beobachtung, dass externe Module ihre eigene Box sind. Es mag ein sehr komplexes Objekt 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 der obersten 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 Verbraucher. Sie können Ihren Typ nach Belieben benennen (t in diesem Fall) und müssen keine unnötigen 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/namespace verwenden:

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 Punkte sind rote Fahnen für die Modulstrukturierung. Stellen Sie sicher, dass Sie nicht versuchen, Ihre externen Module mit einem Namespace zu versehen, wenn eine der folgenden Bedingungen für Ihre Dateien gilt:

  • Eine Datei, deren einzige oberste Deklaration export module Foo { ... } Ist (entfernen Sie Foo und verschieben Sie alles um eine Ebene nach oben)
  • Eine Datei mit einem einzelnen export class Oder export function, Der nicht export default Ist
  • Mehrere Dateien mit demselben export module Foo { Auf oberster Ebene (denken Sie nicht, dass diese zu einem Foo zusammengefasst werden!)
747
Ryan Cavanaugh

Nichts ist falsch mit Ryans Antwort, aber für die Leute, die hierher kamen und nach einer one-class-per-file-Struktur suchten, während sie noch korrekt ES6-Namespaces verwenden, lesen Sie bitte die hilfreiche Ressource this von Microsoft .

Unklar ist mir nach dem Lesen des Dokuments: Wie importiere ich das gesamte (zusammengeführte) Modul mit singleimport?.

Edit Zurückkehren, um diese Antwort zu aktualisieren. In TS gibt es einige Ansätze zum Namensraum. 

Alle Modulklassen in einer Datei. 

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

Importieren Sie Dateien in den Namespace und weisen Sie sie erneut 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 abschließende Überlegung. Sie könnten für jede Datei Namespace sein

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

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

Wenn jedoch zwei Klassen aus demselben Namensraum importiert werden, wird TS sich darüber beklagen, dass es einen doppelten Bezeichner gibt. Die einzige Lösung, wie diesmal der Fall ist, ist der Aliasname des Namespaces.

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

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

Dieses Aliasing ist absolut abscheulich, also mach es nicht. Sie sind besser mit einem Ansatz oben. Ich persönlich bevorzuge das "Fass".

46
Jefftopia

Versuchen Sie, nach Ordnern 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 es Ihrem Modul selbst egal sein sollte, dass es an einem Namespace teilnimmt. Dies macht jedoch die API für den Konsumenten auf eine kompakte, vernünftige Art und Weise zugänglich, die unabhängig von dem Typ des für das Projekt verwendeten Modulsystems ist. 

6
Albinofrenchy

Kleine Verbesserung der Antwort von Albino Frenchy:

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);
3
Mike Vitik

OP Ich bin mit dir, Mann .. Auch diesmal ist mit 300+ Stimmen nichts falsch, aber meine Meinung ist:

  1. was ist falsch daran, Klassen einzeln in ihre gemütlichen warmen 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 import import importieren ... importieren Sie einfach jede der Modelldateien wie man, srsly, eine Modelldatei, eine .d.ts-Datei. Warum gibt es so viele * ist da drin? es sollte einfach und aufgeräumt sein, und das ist es. Warum brauche ich dort Importe? Warum? C # hat aus einem bestimmten Grund Namespaces.

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

Leider lautet meine Antwort: nop, Sie können die Sache mit dem Namensraum nicht funktional machen, wenn Sie nicht alle diese Importe verwenden oder diese Dateinamen als Bezeichner verwenden (was ich wirklich dumm finde). Eine weitere Option ist: alle diese Abhängigkeiten in eine Box namens filenameasidentifier.ts aufnehmen und verwenden 

export namespace(or module) boxInBox {} .

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

2
NO... Bugs...

Einige der Fragen/Kommentare, die ich zu diesem Thema gesehen habe, klingen für mich so, als ob die Person Namespace verwendet, wobei sie "Modul-Alias" 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 .js-Ausgabedateien muss irgendwie behandelt werden, z. B. mit diesem https: // github. com/tleunen/babel-plugin-module-resolver

Beispiel .babelrc für die Aliasauflösung:

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

Versuchen Sie dieses Namespaces-Modul

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

Der richtige Weg, um Ihren Code zu organisieren, besteht darin, anstelle von Namespaces separate Verzeichnisse zu verwenden. Jede Klasse befindet sich in einer eigenen Datei, im jeweiligen 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, ist die Navigation viel einfacher und er ist selbstdokumentierend, basierend auf der Verzeichnisstruktur.

// 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);
0
NolePTR

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