¿Cómo uso espacios de nombres con módulos externos TypeScript?

233

Tengo un código:

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 {

  }
}

Todo esto es muy confuso. Quiero tener un grupo de módulos externos contribuyen tipos al mismo espacio de nombres, Living.Things. Parece que esto no funciona en absoluto - no puedo ver Animalen dogs.ts. Tengo que escribir el nombre del espacio de nombres completo b.Living.Things.Planten tree.ts. No funciona combinar varios objetos en el mismo espacio de nombres a través del archivo. ¿Cómo hago esto?

Ryan Cavanaugh
fuente

Respuestas:

860

Analogía de la Copa Candy

Versión 1: una taza para cada dulce

Digamos que escribiste un código como este:

Mod1.ts

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

Mod2.ts

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

Mod3.ts

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

Has creado esta configuración: ingrese la descripción de la imagen aquí

Cada módulo (hoja de papel) recibe su propio vaso con nombre A. Esto es inútil: en realidad no está organizando sus dulces aquí, solo está agregando un paso adicional (sacándolo de la taza) entre usted y las golosinas.


Versión 2: una taza en el alcance global

Si no estaba utilizando módulos, podría escribir código como este (tenga en cuenta la falta de exportdeclaraciones):

global1.ts

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

global2.ts

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

global3.ts

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

Este código crea un espacio Ade nombres combinado en el ámbito global:

ingrese la descripción de la imagen aquí

Esta configuración es útil, pero no se aplica en el caso de módulos (porque los módulos no contaminan el alcance global).


Versión 3: ir sin taza

Volviendo al ejemplo inicial, las copas A, Ay Ano le están haciendo ningún favor. En cambio, podría escribir el código como:

Mod1.ts

export class Twix { ... }

Mod2.ts

export class PeanutButterCup { ... }

Mod3.ts

export class KitKat { ... }

para crear una imagen que se vea así:

ingrese la descripción de la imagen aquí

¡Mucho mejor!

Ahora, si todavía está pensando en cuánto realmente quiere usar el espacio de nombres con sus módulos, siga leyendo ...


Estos no son los conceptos que estás buscando

Necesitamos volver a los orígenes de por qué existen espacios de nombres en primer lugar y examinar si esas razones tienen sentido para los módulos externos.

Organización : los espacios de nombres son útiles para agrupar objetos y tipos relacionados lógicamente. Por ejemplo, en C #, encontrará todos los tipos de colección System.Collections. Al organizar nuestros tipos en espacios de nombres jerárquicos, ofrecemos una buena experiencia de "descubrimiento" para los usuarios de esos tipos.

Conflictos de nombres: los espacios de nombres son importantes para evitar las colisiones de nombres. Por ejemplo, puede tener My.Application.Customer.AddFormy My.Application.Order.AddForm- dos tipos con el mismo nombre, pero un espacio de nombres diferente. En un lenguaje donde todos los identificadores existen en el mismo ámbito raíz y todos los ensamblajes cargan todos los tipos, es fundamental que todo esté en un espacio de nombres.

¿Tienen sentido esos motivos en los módulos externos?

Organización : los módulos externos ya están presentes en un sistema de archivos, necesariamente. Tenemos que resolverlos por ruta y nombre de archivo, por lo que hay un esquema lógico de organización que podemos usar. Podemos tener una /collections/generic/carpeta con un listmódulo.

Conflictos de nombre : esto no se aplica en absoluto en los módulos externos. Dentro de un módulo, no hay una razón plausible para tener dos objetos con el mismo nombre. Desde el punto de vista del consumo, el consumidor de cualquier módulo determinado puede elegir el nombre que usará para referirse al módulo, por lo que los conflictos de nombres accidentales son imposibles.


Incluso si no cree que esas razones se aborden adecuadamente por cómo funcionan los módulos, la "solución" de tratar de usar espacios de nombres en módulos externos ni siquiera funciona.

Cajas en Cajas en Cajas

Una historia:

Tu amigo Bob te llama. "Tengo un gran nuevo esquema de organización en mi casa", dice, "¡ven a verlo!". Bien, vamos a ver qué ha ocurrido Bob.

Empiezas en la cocina y abres la despensa. Hay 60 cajas diferentes, cada una etiquetada "Despensa". Elige una caja al azar y la abre. Dentro hay una sola caja etiquetada "Granos". Abre el cuadro "Granos" y encuentra un cuadro individual con la etiqueta "Pasta". Abre el cuadro "Pasta" y encuentra un cuadro individual con la etiqueta "Penne". Abre esta caja y encuentra, como espera, una bolsa de pasta penne.

Ligeramente confundido, recoges una caja adyacente, también etiquetada como "Despensa". Dentro hay una sola caja, nuevamente etiquetada como "Granos". Abre el cuadro "Granos" y, de nuevo, encuentra un cuadro individual etiquetado como "Pasta". Abre la caja de "Pasta" y encuentra una sola caja, esta etiquetada como "Rigatoni". Abres esta caja y encuentras ... una bolsa de pasta rigatoni.

"¡Es genial!" dice Bob "¡Todo está en un espacio de nombres!".

"Pero Bob ..." respondes. "El esquema de su organización es inútil. Debe abrir un montón de cajas para llegar a cualquier cosa, y en realidad no es más conveniente encontrar nada que si hubiera puesto todo en una caja en lugar de tres . De hecho, desde su la despensa ya está ordenada de un estante a otro, no necesita las cajas en absoluto. ¿Por qué no simplemente coloca la pasta en el estante y la recoge cuando la necesita? "

"No entiendes, tengo que asegurarme de que nadie más ponga algo que no pertenezca en el espacio de nombres 'Despensa'. Y he organizado de manera segura toda mi pasta en el Pantry.Grains.Pastaespacio de nombres para poder encontrarla fácilmente"

Bob es un hombre muy confundido.

Los módulos son su propia caja

Probablemente ha sucedido algo similar en la vida real: usted ordena algunas cosas en Amazon, y cada artículo aparece en su propia caja, con una caja más pequeña dentro, con su artículo envuelto en su propio embalaje. Incluso si las cajas interiores son similares, los envíos no se "combinan" de manera útil.

Siguiendo la analogía de la caja, la observación clave es que los módulos externos son su propia caja . Puede ser un elemento muy complejo con mucha funcionalidad, pero cualquier módulo externo dado es su propia caja.


Orientación para módulos externos

Ahora que hemos descubierto que no necesitamos usar 'espacios de nombres', ¿cómo debemos organizar nuestros módulos? A continuación se presentan algunos principios rectores y ejemplos.

Exporte lo más cerca posible al nivel superior

  • Si solo está exportando una sola clase o función, use 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());

Esto es óptimo para los consumidores. Pueden nombrar su tipo como quieran ( ten este caso) y no tienen que hacer puntos extraños para encontrar sus objetos.

  • Si está exportando varios objetos, colóquelos todos en el nivel superior:

MyThings.ts

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

Consumo

import * as m from './MyThings';
var x = new m.SomeType();
var y = m.someFunc();
  • Si está exportando una gran cantidad de cosas, solo entonces debe usar la palabra clave 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();

Banderas rojas

Todas las siguientes son banderas rojas para la estructuración de módulos. Vuelva a verificar que no está intentando asignar espacios a sus módulos externos si alguno de estos se aplica a sus archivos:

  • Un archivo cuya única declaración de nivel superior es export module Foo { ... }(eliminar Fooy mover todo 'arriba' un nivel)
  • Un archivo que tiene un solo export classo export functionque noexport default
  • Varios archivos que tienen lo mismo export module Foo {en el nivel superior (¡no piense que se combinarán en uno Foo!)
Ryan Cavanaugh
fuente
80
Esta es una no respuesta. La premisa de que no debería necesitar o desear espacios de nombres para módulos externos es errónea. Si bien el sistema de archivos es una especie de esquema de organización que puede un poco utilizar para estos fines, que no es tan agradable para el consumidor tener n declaraciones de importación para el uso de clases o funciones n de un proyecto determinado; especialmente porque también enturbia la convención de nomenclatura cuando no está en el código real.
Albinofrenchy
12
No importa cuánto se quiera, todavía no es posible .
Ryan Cavanaugh
26
No entiendo, ya no estamos escribiendo pascal. ¿Desde cuándo organizar el uso del sistema de archivos es el camino a seguir?
David
99
Puede tener un módulo "envoltorio" que importe y reexporte todo lo que interese a los consumidores de su biblioteca. Pero, de nuevo, el uso de un "espacio de nombres" no proporcionará ningún valor que no sea forzar otro nivel de indirección para cualquiera que use su código.
Ryan Cavanaugh
13
Gran redacción, gracias. Siento que debe vincular a esto desde www.typescriptlang.org/docs/handbook/namespaces.html. Debo haber leído ese enlace de typecriptlang.org 3 o 4 veces y, como desarrollador de C #, naturalmente quiero poner todo en un espacio de nombres. He leído algunas sugerencias que dicen que no, pero sin explicación de por qué y nada tan definitivo (y bien descrito) como esto. Además, nada en los documentos mecanografiados menciona este AFAIK
Adam Plocher
53

No hay nada malo con la respuesta de Ryan, pero para las personas que vinieron aquí buscando cómo mantener una estructura de una clase por archivo mientras todavía usan los espacios de nombres ES6 correctamente, consulte esto útil recurso de Microsoft.

Una cosa que no me queda clara después de leer el documento es: cómo importar todo el módulo (combinado) con un solo import .

Edite Volver en círculo para actualizar esta respuesta. Algunos enfoques para el espacio de nombres surgen en TS.

Todas las clases de módulos en un archivo.

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

Importe archivos al espacio de nombres y reasigne

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

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

Barriles

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

Una consideración final. Usted podría namespace cada archivo

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

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

Pero como uno importa dos clases del mismo espacio de nombres, TS se quejará de que hay un identificador duplicado. La única solución como esta vez es alias el espacio de nombres.

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

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

Este alias es absolutamente aborrecible, así que no lo hagas. Estás mejor con un enfoque anterior. Personalmente, prefiero el 'barril'.

Jefftopia
fuente
66
¿Qué son los "espacios de nombres ES6"?
Aluan Haddad
@AluanHaddad al importar es2015 +, las cosas importadas son predeterminadas, desestructuradas o de espacios de nombres. const fs = require('fs'), fses el espacio de nombres. import * as moment from 'moment', momentes el espacio de nombres. Esto es ontología, no la especificación.
Jefftopia
Soy consciente de eso, pero harías bien en explicarlo en tu respuesta. Sin embargo, los espacios de nombres de ES6 son realmente una cosa, y el requireejemplo no se aplica a ellos por una serie de razones, incluido el hecho de que los espacios de nombres de ES6 pueden no llamarse, mientras que requiredevuelve un objeto simple que bien puede llamarse.
Aluan Haddad
1
No lo sigo, porque si lo importado es invocable o no, todavía sirve como un espacio de nombres lógicamente hablando. No creo que las advertencias sean materiales para mi respuesta anterior.
Jefftopia
7

Intenta organizar por carpeta:

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)

La idea es que su propio módulo no debería preocuparse / saber que está participando en un espacio de nombres, pero esto expone su API al consumidor de una manera compacta y sensata que es independiente del tipo de sistema de módulo que está utilizando para el proyecto.

Albinofrenchy
fuente
8
LivingThings.dog.Dog es lo que tienes aquí.
Corey Alix
Recomiendo mantener la letra mayúscula consistente, si exporta "Árbol", entonces importe "Árbol", no "árbol".
Demisx
1
Además, ¿cómo puede importar algo desde tree.tsque no tiene ningún miembro exportado?
Demisx
Man TS seguramente tiene una sintaxis antigua tonta, como importy requirejuntos en una sola declaración.
Andy
3

Pequeña mejora de la respuesta de 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() { }
} 

cosas.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
fuente
2
¡Gracias por esto! Solo quería decir que los cambios en una respuesta existente no deberían publicarse preferiblemente como nuevas respuestas: deberían agregarse como un comentario a la respuesta existente o (mejor) deberían sugerirse sugiriendo una edición de la respuesta que desea mejorar.
a3nm
3

OP Estoy contigo hombre. de nuevo también, no hay nada de malo en esa respuesta con más de 300 votos, pero mi opinión es:

  1. ¿Qué hay de malo en poner las clases en sus propios archivos cálidos y acogedores individualmente? Quiero decir, esto hará que las cosas se vean mucho mejor, ¿verdad? (o alguien como un archivo de 1000 líneas para todos los modelos)

  2. entonces, si se logra el primero, tenemos que importar importar importar ... importar solo en cada uno de los archivos de modelo como man, srsly, un archivo de modelo, un archivo .d.ts, por qué hay tantos * s ahí? debería ser simple, ordenado, y eso es todo. ¿Por qué necesito importaciones allí? ¿por qué? C # tiene espacios de nombres por una razón.

  3. Y para entonces, literalmente está usando "filenames.ts" como identificadores. Como identificadores ... Vamos en su 2017 ahora y todavía lo hacemos? Voy a volver a Marte y dormir por otros 1000 años.

Lamentablemente, mi respuesta es: no, no puedes hacer que el "espacio de nombres" funcione si no usas todas esas importaciones o usas esos nombres de archivo como identificadores (lo que creo que es realmente tonto). Otra opción es: poner todas esas dependencias en un cuadro llamado filenameasidentifier.ts y usar

export namespace(or module) boxInBox {} .

envuélvalos para que no intenten acceder a otras clases con el mismo nombre cuando simplemente intentan obtener una referencia de la clase que se encuentra justo encima de ellos.

NO ... errores ...
fuente
3

Varias de las preguntas / comentarios que he visto sobre este tema me suenan como si la persona estuviera usando Namespace estuviera donde quieren decir 'alias de módulo'. Como Ryan Cavanaugh mencionó en uno de sus comentarios, puede hacer que un módulo 'Wrapper' vuelva a exportar varios módulos.

Si realmente desea importarlo todo desde el mismo nombre / alias de módulo, combine un módulo contenedor con una asignación de rutas en su tsconfig.json .

Ejemplo:

./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 resolución del módulo en los archivos .js de salida deberá manejarse de alguna manera, como con este https://github.com/tleunen/babel-plugin-module-resolver

Ejemplo .babelrcpara manejar la resolución de alias:

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

Prueba este módulo de espacios de nombres

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 de compilación ---

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
fuente
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
fuente
-1

La forma correcta de organizar su código es usar directorios separados en lugar de espacios de nombres. Cada clase estará en su propio archivo, en su respectiva carpeta de espacio de nombres. index.ts solo reexportará cada archivo; ningún código real debe estar en el archivo index.ts. Organizar su código de esta manera hace que sea mucho más fácil navegar, y se auto documenta en función de la estructura del directorio.

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

Luego lo usarías como tal:

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
fuente
Esto es engañoso y absolutamente incorrecto. No es así como funcionan los espacios de nombres. Tampoco responde la pregunta de operaciones.
AndrewMcLagan