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

Resuelto Ryan Cavanaugh asked hace 9 años • 10 respuestas

Tengo un código:

tiposbase.ts

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

perro.ts

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

export namespace Living.Things {
  // Error, can't find name 'Animal', ??
  export class Dog extends Animal {
    woof() { }
  }
}

árbol.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 montón de módulos externos, todos contribuyan con tipos al mismo espacio de nombres Living.Things. Parece que esto no funciona en absoluto; no puedo ver Animalel archivo dogs.ts. Tengo que escribir el nombre completo del espacio de nombres b.Living.Things.Planten tree.ts. No funciona combinar varios objetos en el mismo espacio de nombres en un archivo. ¿Cómo hago esto?

Ryan Cavanaugh avatar May 21 '15 01:05 Ryan Cavanaugh
Aceptado

Analogía de la taza de caramelo

Versión 1: Una taza por 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 llamado A. Esto es inútil: en realidad no estás organizando tus dulces aquí, solo estás agregando un paso adicional (sacarlos del vaso) entre tú y las golosinas.


Versión 2: Una taza en el ámbito global

Si no estuviera usando 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 de nombres combinado Aen 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 ámbito global).


Versión 3: ir sin copa

Volviendo al ejemplo original, las tazas A, Ay Ano te están haciendo ningún favor. En su lugar, podrías 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ás pensando en cuánto quieres usar realmente el espacio de nombres con tus módulos, sigue leyendo...


Estos no son los conceptos que estás buscando

En primer lugar, debemos volver a los orígenes de por qué existen los espacios de nombres 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 colecciones en System.Collections. Al organizar nuestros tipos en espacios de nombres jerárquicos, brindamos una buena experiencia de "descubrimiento" para los usuarios de esos tipos.

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

¿Esas razones tienen sentido en 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, para que podamos usar un esquema de organización lógico. Podemos tener una /collections/generic/carpeta con un listmódulo en ella.

Conflictos de nombres : esto no se aplica en absoluto en módulos externos. Dentro de un módulo, no hay ninguna 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 utilizará 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 en la forma en que funcionan los módulos, la "solución" de intentar utilizar espacios de nombres en módulos externos ni siquiera funciona.

Cajas en Cajas en Cajas

Una historia:

Tu amigo Bob te llama. "Tengo un nuevo esquema de organización genial en mi casa", dice, "¡ven a verlo!". Genial, veamos qué se le ocurrió a Bob.

Empiezas en la cocina y abres la despensa. Hay 60 cajas diferentes, cada una con la etiqueta "Despensa". Eliges una caja al azar y la abres. En el interior hay una sola caja con la etiqueta "Granos". Abres el cuadro "Granos" y encuentras un solo cuadro llamado "Pasta". Abres el cuadro "Pasta" y encuentras un solo cuadro con la etiqueta "Penne". Abres esta caja y encuentras, como esperas, una bolsa de pasta penne.

Un poco confundido, tomas una caja adyacente, también etiquetada "Despensa". En el interior hay una sola caja, nuevamente etiquetada como "Granos". Abres el cuadro "Granos" y, nuevamente, encuentras un solo cuadro llamado "Pasta". Abres el cuadro "Pasta" y encuentras un solo cuadro, este tiene la etiqueta "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. "Tu esquema de organización es inútil. Tienes que abrir un montón de cajas para llegar a cualquier cosa, y en realidad no es más conveniente encontrar nada que si hubieras puesto todo en una caja en lugar de tres. . De hecho, desde tu La despensa ya está ordenada estante por estante, no necesitas las cajas en absoluto. ¿Por qué no simplemente colocas la pasta en el estante y la recoges cuando la necesitas?

"No lo entiendes. Necesito asegurarme de que nadie más ponga algo que no pertenezca al espacio de nombres 'Despensa'. Y he organizado de forma 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 te haya sucedido algo similar en la vida real: pides algunas cosas en Amazon y cada artículo aparece en su propia caja, con una caja más pequeña dentro, con el artículo envuelto en su propio embalaje. Aunque las cajas interiores sean similares, los envíos no se "combinan" de forma útil.

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


Guía 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.

Exportar lo más cerca posible del nivel superior

  • Si solo está exportando una única clase o función, use export default:

MiClase.ts

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

MisFunc.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 ningún punto extraño para encontrar sus objetos.

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

MisCosas.ts

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

Consumo

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

Todo lo siguiente son señales de alerta para la estructuración de módulos. Vuelva a verificar que no esté intentando asignar espacios de nombres 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 'hacia arriba' un nivel)
  • Un archivo que tiene un único export classo export functionque no lo esexport default
  • Varios archivos que tienen lo mismo export module Foo {en el nivel superior (¡no crea que se van a combinar en uno solo Foo!)
Ryan Cavanaugh avatar May 20 '2015 18:05 Ryan Cavanaugh

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

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

Editar Volviendo atrás para actualizar esta respuesta. En TS surgen algunos enfoques para el espacio de nombres.

Todas las clases de módulos en un solo archivo.

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

Importar archivos al espacio de nombres y reasignarlos

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. Podrías asignar un espacio a cada archivo

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

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

Pero cuando uno importa dos clases del mismo espacio de nombres, TS se quejará de que hay un identificador duplicado. La única solución en esta ocasión es asignar un alias al 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. Es mejor seguir el enfoque anterior. Personalmente prefiero el 'barril'.

Jefftopia avatar Feb 07 '2016 17:02 Jefftopia

Intenta organizar por carpeta:

tiposbase.ts

export class Animal {
    move() { /* ... */ }
}

export class Plant {
    photosynthesize() { /* ... */ }
}

perro.ts

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

export class Dog extends b.Animal {
    woof() { }
}   

árbol.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
}

principal.ts

import LivingThings = require('./LivingThings');
console.log(LivingThings.Tree)
console.log(LivingThings.Dog)

La idea es que a su módulo no le debería importar ni 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 avatar Aug 12 '2015 23:08 Albinofrenchy

Pruebe este módulo de espacios de nombres

espacio de nombresModuleFile.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);
    }
}
}

libroTreeCombine.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 avatar Oct 23 '2018 15:10 Bal mukund kumar

Varias de las preguntas/comentarios que he visto sobre este tema me suenan como si la persona estuviera usando Namespace"alias de módulo". Como mencionó Ryan Cavanaugh 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 archivo 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 avatar Nov 01 '2017 02:11 Ryan Thomas