Come uso gli spazi dei nomi con i moduli esterni TypeScript?


233

Ho del codice:

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 {

  }
}

Questo è tutto molto confuso. Voglio avere un gruppo di moduli esterni contribuiscono tutti i tipi allo stesso spazio dei nomi, Living.Things. Sembra che questo non funziona a tutti - Non riesco a vedere Animalin dogs.ts. Devo scrivere il nome completo dello spazio dei nomi b.Living.Things.Plantin tree.ts. Non funziona per combinare più oggetti nello stesso spazio dei nomi nel file. Come faccio a fare questo?

Risposte:


860

Analogia Candy Cup

Versione 1: una tazza per ogni caramella

Diciamo che hai scritto un codice come questo:

Mod1.ts

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

Mod2.ts

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

Mod3.ts

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

Hai creato questa configurazione: inserisci qui la descrizione dell'immagine

A ciascun modulo (foglio di carta) viene assegnato un proprio nome A. Questo è inutile: non stai organizzando le tue caramelle qui, stai solo aggiungendo un ulteriore passaggio (togliendolo dalla tazza) tra te e le prelibatezze.


Versione 2: una tazza nell'ambito globale

Se non stavi utilizzando i moduli, potresti scrivere codice in questo modo (nota la mancanza di export dichiarazioni):

global1.ts

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

global2.ts

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

global3.ts

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

Questo codice crea uno spazio dei nomi unito Anell'ambito globale:

inserisci qui la descrizione dell'immagine

Questa configurazione è utile, ma non si applica nel caso dei moduli (poiché i moduli non inquinano l'ambito globale).


Versione 3: andare senza coppa

Tornando all'esempio originale, le tazze A, Ae Anon stanno facendo qualsiasi favori. Invece, potresti scrivere il codice come:

Mod1.ts

export class Twix { ... }

Mod2.ts

export class PeanutButterCup { ... }

Mod3.ts

export class KitKat { ... }

per creare un'immagine simile a questa:

inserisci qui la descrizione dell'immagine

Molto meglio!

Ora, se stai ancora pensando a quanto vuoi veramente usare lo spazio dei nomi con i tuoi moduli, continua a leggere ...


Questi non sono i concetti che stai cercando

Dobbiamo tornare alle origini del perché gli spazi dei nomi esistono in primo luogo ed esaminare se tali ragioni hanno senso per i moduli esterni.

Organizzazione : gli spazi dei nomi sono utili per raggruppare oggetti e tipi logicamente correlati. Ad esempio, in C #, troverai tutti i tipi di raccolta inSystem.Collections . Organizzando i nostri tipi in spazi dei nomi gerarchici, forniamo una buona esperienza di "scoperta" per gli utenti di quei tipi.

Conflitti di nomi: gli spazi dei nomi sono importanti per evitare la collisione dei nomi. Ad esempio, potresti avere My.Application.Customer.AddForme My.Application.Order.AddForm- due tipi con lo stesso nome, ma uno spazio dei nomi diverso. In una lingua in cui tutti gli identificatori esistono nello stesso ambito radice e tutti gli assembly caricano tutti i tipi, è fondamentale che tutto sia nello spazio dei nomi.

Queste ragioni hanno senso nei moduli esterni?

Organizzazione : i moduli esterni sono già presenti in un file system, necessariamente. Dobbiamo risolverli per percorso e nome file, quindi c'è uno schema logico di organizzazione che possiamo usare. Possiamo avere una /collections/generic/cartella con alist modulo al suo interno.

Conflitti di nomi : questo non si applica affatto ai moduli esterni. All'interno di un modulo, non esiste alcun motivo plausibile per avere due oggetti con lo stesso nome. Dal punto di vista del consumo, il consumatore di un determinato modulo può scegliere il nome che utilizzerà per fare riferimento al modulo, quindi i conflitti di denominazione accidentale sono impossibili.


Anche se non credete che tali ragioni siano adeguatamente affrontate dal modo in cui funzionano i moduli, la "soluzione" di provare a usare spazi dei nomi in moduli esterni non funziona nemmeno.

Scatole in scatole in scatole

Una storia:

Il tuo amico Bob ti chiama. "Ho un grande nuovo schema organizzativo nella mia casa", dice, "vieni a dare un'occhiata!". Bene, andiamo a vedere cosa ha inventato Bob.

Inizi in cucina e apri la dispensa. Ci sono 60 scatole diverse, ciascuna etichettata "Pantry". Scegli una scatola a caso e la apri. All'interno c'è una singola scatola con l'etichetta "Grani". Apri la casella "Grani" e trova una singola casella con l'etichetta "Pasta". Apri la casella "Pasta" e trova una casella singola denominata "Penne". Apri questa scatola e trova, come ti aspetti, un sacchetto di penne.

Leggermente confuso, raccogli una scatola adiacente, anche etichettata "Dispensa". All'interno c'è una singola scatola, di nuovo etichettata "Grani". Apri la casella "Grani" e, di nuovo, trovi una singola scatola con l'etichetta "Pasta". Apri la scatola "Pasta" e trovi una scatola singola, questa è etichettata "Rigatoni". Apri questa scatola e trova ... un sacchetto di rigatoni.

"È ottimo!" dice Bob. "Tutto è in uno spazio dei nomi!".

"Ma Bob ..." rispondi. "Il tuo schema organizzativo è inutile. Devi aprire un mucchio di scatole per arrivare a qualsiasi cosa, e in realtà non è più conveniente trovare qualcosa che se avessi semplicemente messo tutto in una scatola anziché tre . In effetti, dal momento che il tuo la dispensa è già ordinata scaffale per scaffale, non hai bisogno delle scatole. Perché non mettere la pasta sullo scaffale e raccoglierla quando ne hai bisogno? "

"Non capisci - Devo assicurarmi che nessun altro metta qualcosa che non appartiene allo spazio dei nomi" Pantry ". E ho organizzato in modo sicuro tutta la mia pasta nello Pantry.Grains.Pastaspazio dei nomi in modo da poterlo facilmente trovare"

Bob è un uomo molto confuso.

I moduli sono la loro scatola

Probabilmente hai avuto qualcosa di simile accadere nella vita reale: ordini alcune cose su Amazon e ogni articolo viene mostrato nella sua scatola, con una scatola più piccola all'interno, con l'articolo avvolto nella sua confezione. Anche se le scatole interne sono simili, le spedizioni non vengono "combinate" utilmente.

Seguendo l'analogia della scatola, l'osservazione chiave è che i moduli esterni sono la loro scatola . Potrebbe essere un articolo molto complesso con molte funzionalità, ma ogni dato modulo esterno è la sua scatola.


Guida per moduli esterni

Ora che abbiamo capito che non abbiamo bisogno di usare "spazi dei nomi", come dovremmo organizzare i nostri moduli? Seguono alcuni principi guida ed esempi.

Esporta il più vicino possibile al livello più alto

  • Se esporti solo una singola classe o funzione, usa export default:

MyClass.ts

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

MyFunc.ts

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

Consumo

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

Questo è ottimale per i consumatori. Possono nominare il tuo tipo come preferiscono ( tin questo caso) e non devono fare punti estranei per trovare i tuoi oggetti.

  • Se esporti più oggetti, mettili tutti ai massimi livelli:

MyThings.ts

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

Consumo

import * as m from './MyThings';
var x = new m.SomeType();
var y = m.someFunc();
  • Se stai esportando un gran numero di cose, solo allora dovresti usare la parola chiave module/ namespace:

MyLargeModule.ts

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

Consumo

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

Bandiere rosse

Tutte le seguenti sono bandiere rosse per la strutturazione del modulo. Controlla di nuovo che non stai provando a namespace dei tuoi moduli esterni se qualcuno di questi si applica ai tuoi file:

  • Un file la cui unica dichiarazione di livello superiore è export module Foo { ... }(rimuovere Fooe spostare tutto 'su' un livello)
  • Un file che ha un singolo export classo export functionche non lo èexport default
  • Più file che hanno lo stesso export module Foo {livello di primo livello (non pensare che questi si uniranno in uno Foo!)

80
Questa è una non risposta. La premessa che non dovresti avere bisogno o desideri spazi dei nomi per i moduli esterni è difettosa. Mentre il file system è una sorta di schema di organizzazione è possibile un po utilizzare per questi scopi, non è altrettanto bello per il consumatore di avere n istruzioni import per l'utilizzo di n classi o funzioni da un determinato progetto; soprattutto perché confonde anche la convenzione di denominazione quando si è inattivi nel codice reale.
Albinofrenchy,

12
Non importa quanto si possa desiderare, non è ancora possibile .
Ryan Cavanaugh,

26
Non capisco, non stiamo più scrivendo Pascal. Da quando l'organizzazione sta utilizzando il file system la strada da percorrere?
David,

9
Puoi avere un modulo "wrapper" che importa e riesporta tutto ciò che interessa ai consumatori della tua biblioteca. Ma ancora una volta, usando uno "spazio dei nomi" non si otterrà alcun valore se non quello di forzare un altro livello di riferimento indiretto per chiunque utilizzi il proprio codice.
Ryan Cavanaugh,

13
Ottima scrittura, grazie. Sento che dovresti collegarti a questo da www.typescriptlang.org/docs/handbook/namespaces.html. Devo aver letto quel link typescriptlang.org 3 o 4 volte e come sviluppatore di C #, voglio naturalmente mettere tutto in uno spazio dei nomi. Ho letto alcuni suggerimenti dicendo di non farlo, ma senza una spiegazione del perché e niente di definitivo (e ben descritto) come questo. Inoltre nulla nei documenti dattiloscritti menziona questo AFAIK
Adam Plocher il

53

Nulla di sbagliato nella risposta di Ryan, ma per le persone che sono venute qui alla ricerca di come mantenere una struttura di classe per file pur usando correttamente gli spazi dei nomi ES6, fare riferimento a questa utile risorsa di Microsoft.

Una cosa che non mi è chiara dopo aver letto il documento è: come importare l'intero modulo (unito) con un singolo import .

Modifica Tornando indietro per aggiornare questa risposta. Alcuni approcci allo spazio dei nomi emergono in TS.

Tutte le classi di moduli in un unico file.

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

Importa i file nello spazio dei nomi e riassegna

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

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

botti

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

Un'ultima considerazione. È possibile namespace ogni file

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

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

Ma quando si importano due classi dallo stesso spazio dei nomi, TS si lamenterà che esiste un identificatore duplicato. L'unica soluzione in questo momento è quindi alias dello spazio dei nomi.

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

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

Questo aliasing è assolutamente disgustoso, quindi non farlo. Stai meglio con un approccio sopra. Personalmente, preferisco la "canna".


6
Cosa sono gli "namespace ES6"?
Aluan Haddad,

@AluanHaddad durante l'importazione di es2015 +, le cose importate sono predefinite, destrutturate o spaziate dai nomi. const fs = require('fs'), fsè lo spazio dei nomi. import * as moment from 'moment', momentè lo spazio dei nomi. Questa è ontologia, non la specifica.
Jefftopia,

Ne sono consapevole ma faresti bene a spiegarlo nella tua risposta. Gli spazi dei nomi ES6 sono in realtà una cosa, tuttavia, e l' requireesempio non si applica ad essi per una serie di ragioni, incluso il fatto che gli spazi dei nomi ES6 non possono essere chiamati, mentre requirerestituisce un oggetto semplice che può essere facilmente richiamabile.
Aluan Haddad,

1
Non lo seguo, perché se la cosa importata è richiamabile o meno serve ancora logicamente come spazio dei nomi . Non credo che le avvertenze siano rilevanti per la mia risposta sopra.
Jefftopia,

7

Prova a organizzare per cartella:

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)

L'idea è che il modulo stesso non dovrebbe interessarsi / sapere che sta partecipando a uno spazio dei nomi, ma questo espone l'API al consumatore in un modo compatto e sensibile che è agnostico rispetto al tipo di sistema del modulo che stai usando per il progetto.


8
LivingThings.dog.Dog è ciò che hai qui.
Corey Alix,

Consiglio di mantenere coerente la lettera maiuscola, se esporti "Albero", quindi importa "Albero", non "albero".
Demisx,

1
Inoltre, come puoi importare qualcosa da tree.tsquando non ha alcun membro esportato?
Demisx,

Man TS ha sicuramente una vecchia sintassi sciocca, simile importe requireinsieme in una frase.
Andy,

3

Piccolo miglioramento della risposta Albinofrenchy:

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

2
Grazie per questo! Volevo solo dire che le modifiche a una risposta esistente non dovrebbero preferibilmente essere pubblicate come nuove risposte: dovrebbero essere aggiunte come commento alla risposta esistente o (meglio) dovrebbero essere suggerite suggerendo una modifica alla risposta che si desidera Ottimizzare.
a3nm,

3

OP Sono con te amico. anche in questo caso, non c'è nulla di sbagliato in quella risposta con oltre 300 voti, ma la mia opinione è:

  1. cosa c'è di sbagliato nel mettere le classi nei loro file personali caldi e accoglienti individualmente? Voglio dire, questo renderà le cose molto meglio, no? (o qualcuno come un file di 1000 righe per tutti i modelli)

  2. quindi, se il primo sarà raggiunto, dobbiamo importare import import ... importare in ognuno dei file modello come man, srsly, un file modello, un file .d.ts, perché ce ne sono così tanti * è li dentro? dovrebbe essere semplice, ordinato e basta. Perché ho bisogno di importazioni lì? perché? C # ha gli spazi dei nomi per un motivo.

  3. E a quel punto, stai letteralmente usando "filenames.ts" come identificatori. Come identificatori ... Vieni nel 2017 adesso e lo facciamo ancora? Ima torna su Marte e dorme per altri 1000 anni.

Quindi, purtroppo, la mia risposta è: no, non puoi rendere funzionale la cosa dello "spazio dei nomi" se non usi tutte quelle importazioni o usi quei nomi di file come identificatori (che penso sia davvero sciocco). Un'altra opzione è: metti tutte quelle dipendenze in una casella chiamata filenameasidentifier.ts e usa

export namespace(or module) boxInBox {} .

avvolgerli in modo che non provino ad accedere ad altre classi con lo stesso nome quando stanno semplicemente cercando di ottenere un riferimento dalla classe che si siede proprio sopra di loro.


3

Molte delle domande / commenti che ho visto intorno a questo argomento mi sembrano come se la persona stesse usando Namespacedove significano "alias del modulo". Come Ryan Cavanaugh ha menzionato in uno dei suoi commenti, puoi avere un modulo "Wrapper" riesportare diversi moduli.

Se vuoi davvero importare tutto dallo stesso nome / alias del modulo, combina un modulo wrapper con una mappatura dei percorsi nel tuo tsconfig.json.

Esempio:

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

Nota : la risoluzione del modulo nei file .js di output dovrà essere gestita in qualche modo, ad esempio con questo https://github.com/tleunen/babel-plugin-module-resolver

Esempio .babelrcper gestire la risoluzione alias:

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

1

Prova questo modulo di namespace

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

--- parte della compilation ---

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

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

-1

Il modo corretto di organizzare il codice è utilizzare directory separate al posto degli spazi dei nomi. Ogni classe sarà nel proprio file, nella rispettiva cartella dello spazio dei nomi. index.ts riesporterà solo ogni file; nessun codice effettivo dovrebbe essere nel file index.ts. Organizzare il codice in questo modo semplifica notevolmente la navigazione e si auto documenta in base alla struttura delle directory.

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

Lo useresti quindi come tale:

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

Questo è fuorviante e assolutamente errato. Non è così che funzionano gli spazi dei nomi. Inoltre non risponde alla domanda operativa.
AndrewMcLagan
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.