Comment utiliser les espaces de noms avec des modules externes TypeScript?

233

J'ai du 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 {

  }
}

Tout cela est très déroutant. Je veux avoir un tas de modules externes contribuent tous les types au même espace de noms, Living.Things. Il semble que cela ne fonctionne pas du tout - je ne vois pas Animaldans dogs.ts. Je dois écrire le nom complet de l' espace de noms b.Living.Things.Plantdans tree.ts. Il ne fonctionne pas pour combiner plusieurs objets dans le même espace de noms à travers le fichier. Comment puis-je faire cela?

Ryan Cavanaugh
la source

Réponses:

860

Candy Cup Analogy

Version 1: une tasse pour chaque bonbon

Disons que vous avez écrit du code comme celui-ci:

Mod1.ts

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

Mod2.ts

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

Mod3.ts

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

Vous avez créé cette configuration: entrez la description de l'image ici

Chaque module (feuille de papier) a son propre gobelet nommé A. Cela ne sert à rien - vous n'organisez pas réellement vos bonbons ici, vous ajoutez simplement une étape supplémentaire (en le sortant de la tasse) entre vous et les friandises.


Version 2: une tasse à l'échelle mondiale

Si vous n'utilisiez pas de modules, vous pourriez écrire du code comme celui-ci (notez le manque de exportdéclarations):

global1.ts

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

global2.ts

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

global3.ts

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

Ce code crée un espace Ade noms fusionné dans la portée globale:

entrez la description de l'image ici

Cette configuration est utile, mais ne s'applique pas dans le cas des modules (car les modules ne polluent pas la portée globale).


Version 3: aller sans coupe

Pour en revenir à l'exemple d' origine, les tasses A, Aet Ane sont pas vous faire des faveurs. Au lieu de cela, vous pouvez écrire le code comme suit:

Mod1.ts

export class Twix { ... }

Mod2.ts

export class PeanutButterCup { ... }

Mod3.ts

export class KitKat { ... }

pour créer une image qui ressemble à ceci:

entrez la description de l'image ici

Bien mieux!

Maintenant, si vous pensez toujours à combien vous voulez vraiment utiliser l'espace de noms avec vos modules, lisez la suite ...


Ce ne sont pas les concepts que vous recherchez

Nous devons revenir aux origines de la raison pour laquelle les espaces de noms existent en premier lieu et examiner si ces raisons ont un sens pour les modules externes.

Organisation : les espaces de noms sont pratiques pour regrouper des objets et des types liés de manière logique. Par exemple, en C #, vous allez trouver tous les types de collection dans System.Collections. En organisant nos types en espaces de noms hiérarchiques, nous offrons une bonne expérience de «découverte» aux utilisateurs de ces types.

Conflits de noms: les espaces de noms sont importants pour éviter les collisions de noms. Par exemple, vous pouvez avoir My.Application.Customer.AddFormet My.Application.Order.AddForm- deux types avec le même nom, mais un espace de noms différent. Dans un langage où tous les identificateurs existent dans la même portée racine et tous les assemblys chargent tous les types, il est essentiel que tout soit dans un espace de noms.

Ces raisons ont-elles un sens dans les modules externes?

Organisation : les modules externes sont déjà présents dans un système de fichiers, nécessairement. Nous devons les résoudre par chemin et nom de fichier, il y a donc un schéma d'organisation logique à utiliser. Nous pouvons avoir un /collections/generic/dossier contenant un listmodule.

Conflits de noms : cela ne s'applique pas du tout dans les modules externes. Dans un module, il n'y a aucune raison plausible d'avoir deux objets avec le même nom. Du côté de la consommation, le consommateur d'un module donné peut choisir le nom qu'il utilisera pour se référer au module, de sorte que les conflits de nommage accidentels sont impossibles.


Même si vous ne pensez pas que ces raisons sont correctement traitées par le fonctionnement des modules, la «solution» consistant à essayer d'utiliser des espaces de noms dans des modules externes ne fonctionne même pas.

Boîtes en Boîtes en Boîtes

Une histoire:

Votre ami Bob vous appelle. "J'ai un nouveau plan d'organisation formidable dans ma maison", dit-il, "venez le vérifier!". Bien, allons voir ce que Bob a imaginé.

Vous commencez dans la cuisine et ouvrez le garde-manger. Il y a 60 boîtes différentes, chacune intitulée "Garde-manger". Vous choisissez une boîte au hasard et l'ouvrez. A l'intérieur se trouve une seule boîte intitulée "Grains". Vous ouvrez la boîte "Grains" et trouvez une seule boîte intitulée "Pâtes". Vous ouvrez la boîte "Pâtes" et trouvez une seule boîte intitulée "Penne". Vous ouvrez cette boîte et trouvez, comme vous vous en doutez, un sachet de pâtes penne.

Légèrement confus, vous prenez une boîte adjacente, également étiquetée "Garde-manger". A l'intérieur se trouve une seule boîte, à nouveau étiquetée "Grains". Vous ouvrez la boîte "Grains" et, encore une fois, vous trouvez une seule boîte intitulée "Pâtes". Vous ouvrez la boîte "Pasta" et trouvez une seule boîte, celle-ci est étiquetée "Rigatoni". Vous ouvrez cette boîte et trouvez ... un sac de pâtes rigatoni.

"C'est bien!" dit Bob. "Tout est dans un espace de noms!".

"Mais Bob ..." répondez-vous. "Votre schéma d'organisation est inutile. Vous devez ouvrir un tas de boîtes pour accéder à quoi que ce soit, et il n'est pas plus pratique de trouver quoi que ce soit que si vous veniez de tout mettre dans une seule boîte au lieu de trois . En fait, depuis votre le garde-manger est déjà trié étagère par étagère, vous n'avez pas du tout besoin des boîtes. Pourquoi ne pas simplement mettre les pâtes sur l'étagère et les ramasser quand vous en avez besoin? "

"Vous ne comprenez pas - je dois m'assurer que personne d'autre ne place quelque chose qui n'appartient pas à l'espace de noms" Pantry ". Et j'ai organisé toutes mes pâtes en toute sécurité dans l' Pantry.Grains.Pastaespace de noms afin que je puisse facilement le trouver"

Bob est un homme très confus.

Les modules sont leur propre boîte

Vous avez probablement eu quelque chose de similaire dans la vie réelle: vous commandez quelques choses sur Amazon, et chaque article apparaît dans sa propre boîte, avec une petite boîte à l'intérieur, avec votre article emballé dans son propre emballage. Même si les boîtes intérieures sont similaires, les envois ne sont pas utilement «combinés».

Pour l'analogie avec la boîte, l'observation clé est que les modules externes sont leur propre boîte . Ce pourrait être un élément très complexe avec beaucoup de fonctionnalités, mais tout module externe donné est sa propre boîte.


Guide pour les modules externes

Maintenant que nous avons compris que nous n'avons pas besoin d'utiliser des «espaces de noms», comment devrions-nous organiser nos modules? Voici quelques principes directeurs et exemples.

Exportez le plus près possible du niveau supérieur

  • Si vous n'exportez qu'une seule classe ou fonction, utilisez export default:

MyClass.ts

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

MyFunc.ts

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

Consommation

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

Ceci est optimal pour les consommateurs. Ils peuvent nommer votre type comme ils le souhaitent ( tdans ce cas) et n'ont pas à faire de pointage étranger pour trouver vos objets.

  • Si vous exportez plusieurs objets, placez-les tous au niveau supérieur:

MyThings.ts

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

Consommation

import * as m from './MyThings';
var x = new m.SomeType();
var y = m.someFunc();
  • Si vous exportez un grand nombre de choses, alors seulement devez-vous utiliser le mot module- namespaceclé / :

MyLargeModule.ts

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

Consommation

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

Drapeaux rouges

Tous les éléments suivants sont des drapeaux rouges pour la structuration des modules. Vérifiez que vous n'essayez pas de nommer vos modules externes si l'un de ces éléments s'applique à vos fichiers:

  • Un fichier dont la seule déclaration de niveau supérieur est export module Foo { ... }(supprimer Fooet déplacer tout «vers le haut» d'un niveau)
  • Un fichier qui a un seul export classou export functionqui n'est pasexport default
  • Plusieurs fichiers qui ont le même export module Foo {niveau supérieur (ne pensez pas qu'ils vont se combiner en un seul Foo!)
Ryan Cavanaugh
la source
80
Ceci est une non-réponse. La prémisse selon laquelle vous ne devriez pas avoir besoin ou vouloir des espaces de noms pour les modules externes est défectueuse. Bien que le système de fichiers est une sorte de schéma d'organisation , vous pouvez un peu utiliser à ces fins, il est loin d'être aussi agréable pour le consommateur d'avoir n déclarations d'importation pour l' utilisation de n classes ou des fonctions d'un projet donné; d'autant plus que cela brouille également la convention de dénomination lorsque vous êtes dans le code réel.
Albinofrenchy
12
Peu importe combien on pourrait le vouloir, ce n'est toujours pas possible .
Ryan Cavanaugh
26
Je ne comprends pas, nous n'écrivons plus de Pascal. Depuis quand organiser le système de fichiers est-il la voie à suivre?
David
9
Vous pouvez en ayant un module «wrapper» qui importe et réexporte tout ce qui intéresse les consommateurs de votre bibliothèque. Mais encore une fois, en utilisant un «espace de noms», il ne fournira aucune valeur autre que de forcer un autre niveau d'indirection pour quiconque utilise votre code.
Ryan Cavanaugh
13
Excellent article, merci. Je pense que vous devriez créer un lien vers cela depuis www.typescriptlang.org/docs/handbook/namespaces.html. J'ai dû lire ce lien typescriptlang.org 3 ou 4 fois et en tant que développeur C #, je veux naturellement tout mettre dans un espace de noms. J'ai lu quelques suggestions disant de ne pas le faire, mais sans explication pourquoi et rien d'aussi définitif (et bien décrit) que celui-ci. De plus, rien dans les documents dactylographiés ne mentionne cet AFAIK
Adam Plocher
53

Rien de mal à la réponse de Ryan, mais pour les personnes qui sont venus ici à la recherche de la façon de maintenir une structure d' une classe par fichier tout en utilisant correctement les espaces de noms ES6, veuillez vous référer à cette ressource utile de Microsoft.

Une chose qui n'est pas claire pour moi après avoir lu le document est: comment importer le module entier (fusionné) avec un seul import .

Modifiez Encercler pour mettre à jour cette réponse. Quelques approches de l'espace de noms émergent dans TS.

Toutes les classes de modules dans un seul fichier.

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

Importez des fichiers dans l'espace de noms et réaffectez

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

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

Barils

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

Une dernière considération. Vous pouvez nommer chaque fichier

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

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

Mais comme on importe deux classes du même espace de noms, TS se plaindra qu'il y a un identifiant en double. La seule solution, cette fois, consiste à alias l'espace de noms.

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

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

Cet aliasing est absolument odieux, alors ne le faites pas. Vous êtes mieux avec une approche ci-dessus. Personnellement, je préfère le «baril».

Jefftopia
la source
6
Que sont les "espaces de noms ES6"?
Aluan Haddad
@AluanHaddad lors de l'importation d'es2015 +, les éléments importés sont par défaut, déstructurés ou à espace de noms. const fs = require('fs'), fsest l'espace de noms. import * as moment from 'moment', momentest l'espace de noms. C'est l'ontologie, pas la spécification.
Jefftopia
J'en suis conscient, mais vous feriez bien de l'expliquer dans votre réponse. Les espaces de noms ES6 sont en fait une chose, cependant, et l' requireexemple ne s'applique pas à eux pour un certain nombre de raisons, y compris le fait que les espaces de noms ES6 peuvent ne pas être appelés, alors qu'il requirerenvoie un objet simple qui peut très bien être appelable.
Aluan Haddad
1
Je ne suis pas, parce que si la chose importée est appelable ou non, elle sert toujours logiquement d' espace de noms . Je ne pense pas que les mises en garde soient importantes pour ma réponse ci-dessus.
Jefftopia
7

Essayez d'organiser par dossier:

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'idée est que votre module lui-même ne devrait pas se soucier / savoir qu'il participe à un espace de noms, mais cela expose votre API au consommateur d'une manière compacte et sensible qui est indépendante du type de système de modules que vous utilisez pour le projet.

Albinofrenchy
la source
8
LivingThings.dog.Dog est ce que vous avez ici.
Corey Alix
Je recommande de conserver la casse des lettres cohérente, si vous exportez "Tree", puis importez "Tree", pas "tree".
demisx
1
De plus, comment pouvez-vous importer quoi tree.tsque ce soit à partir du moment où il n'a aucun membre exporté?
demisx
Man TS a certainement une vieille syntaxe idiote, comme importet requireensemble dans une seule déclaration.
Andy
3

Petit appauvrissement d'Albinofrenchy réponse:

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);
Mike Vitik
la source
2
Merci pour cela! Je voulais juste dire que les modifications apportées à une réponse existante ne devraient de préférence pas être publiées en tant que nouvelles réponses: elles devraient être ajoutées en tant que commentaire à la réponse existante, ou (mieux) devraient être suggérées en suggérant une modification de la réponse que vous souhaitez améliorer.
a3nm
3

OP, je suis avec toi mec. encore une fois, il n'y a rien de mal à cette réponse avec plus de 300 votes positifs, mais mon avis est:

  1. qu'est-ce qui ne va pas avec le fait de mettre les classes dans leurs propres fichiers chaleureux et individuels? Je veux dire que cela rendra les choses beaucoup mieux, non? (ou quelqu'un comme un fichier de 1000 lignes pour tous les modèles)

  2. alors alors, si le premier est réalisé, il faut importer importer importer ... importer juste dans chacun des fichiers modèle comme man, srsly, un fichier modèle, un fichier .d.ts, pourquoi il y en a tellement * s là-dedans? ça devrait être simple, bien rangé, et c'est tout. Pourquoi ai-je besoin d'importations là-bas? Pourquoi? C # a des espaces de noms pour une raison.

  3. Et d'ici là, vous utilisez littéralement "filenames.ts" comme identificateurs. Comme identifiants ... Venez sur son 2017 maintenant et nous le faisons toujours? Ima retourne sur Mars et dort encore 1000 ans.

Donc, malheureusement, ma réponse est: non, vous ne pouvez pas rendre la chose "namespace" fonctionnelle si vous n'utilisez pas toutes ces importations ou en utilisant ces noms de fichiers comme identifiants (ce qui, je pense, est vraiment idiot). Une autre option est: mettez toutes ces dépendances dans une boîte appelée filenameasidentifier.ts et utilisez

export namespace(or module) boxInBox {} .

enveloppez-les pour qu'ils n'essaient pas d'accéder à d'autres classes du même nom lorsqu'ils essaient simplement d'obtenir une référence de la classe juste au-dessus d'eux.

NON ... Bugs ...
la source
3

Plusieurs des questions / commentaires que j'ai vus autour de ce sujet me semblent comme si la personne utilisait Namespaceoù ils voulaient dire «alias de module». Comme Ryan Cavanaugh l'a mentionné dans l'un de ses commentaires, vous pouvez avoir un module «Wrapper» réexportant plusieurs modules.

Si vous voulez vraiment tout importer du même nom / alias de module, combinez un module wrapper avec un mappage de chemins dans votre tsconfig.json.

Exemple:

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

Remarque : La résolution du module dans les fichiers de sortie .js devra être gérée d'une manière ou d'une autre, comme avec ce https://github.com/tleunen/babel-plugin-module-resolver

Exemple .babelrcpour gérer la résolution d'alias:

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

Essayez ce module d'espaces de noms

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

--- partie de 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');
Bal mukund kumar
la source
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
la source
-1

La bonne façon d'organiser votre code consiste à utiliser des répertoires distincts à la place des espaces de noms. Chaque classe sera dans son propre fichier, dans son dossier d'espace de noms respectif. index.ts ne réexportera que chaque fichier; aucun code réel ne doit se trouver dans le fichier index.ts. Organiser votre code comme celui-ci facilite la navigation et est auto-documenté en fonction de la structure du répertoire.

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

Vous l'utiliseriez alors comme tel:

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
la source
C'est trompeur et absolument incorrect. Ce n'est pas ainsi que fonctionnent les espaces de noms. De plus, cela ne répond pas à la question des opérations.
AndrewMcLagan