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?
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:
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.
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:
Diese Konfiguration ist nützlich, gilt jedoch nicht für Module (da Module den globalen Bereich nicht verschmutzen).
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:
Viel besser!
Wenn Sie noch immer darüber nachdenken, wie viel Namespace Sie wirklich für Ihre Module verwenden möchten, lesen Sie weiter ...
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.
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.
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.
Nachdem wir herausgefunden haben, dass wir keine 'Namespaces' verwenden müssen, wie sollen wir unsere Module organisieren? Es folgen einige Leitprinzipien und Beispiele.
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.
MyThings.ts
export class SomeType { ... }
export function someFunc() { ... }
Verbrauch
import * as m from './MyThings';
var x = new m.SomeType();
var y = m.someFunc();
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();
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:
export module Foo { ... }
Ist (entfernen Sie Foo
und verschieben Sie alles um eine Ebene nach oben)export class
Oder export function
, Der nicht export default
Istexport module Foo {
Auf oberster Ebene (denken Sie nicht, dass diese zu einem Foo
zusammengefasst werden!)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".
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.
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);
OP Ich bin mit dir, Mann .. Auch diesmal ist mit 300+ Stimmen nichts falsch, aber meine Meinung ist:
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)
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.
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.
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 ...
]
}
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');
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);
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 {
}
}