Como uso namespaces com módulos externos TypeScript?

233

Eu tenho algum 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 {

  }
}

Isto é tudo muito confuso. Eu quero ter um monte de módulos externos, todos contribuem com tipos para o mesmo espaço para nome Living.Things,. Parece que isso não funciona em tudo - eu não posso ver Animalno dogs.ts. Eu tenho que escrever o nome namespace completo b.Living.Things.Plantem tree.ts. Não funciona para combinar vários objetos no mesmo espaço para nome no arquivo. Como eu faço isso?

Ryan Cavanaugh
fonte

Respostas:

860

Candy Cup Analogy

Versão 1: Um copo para cada doce

Digamos que você escreveu um 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 { ... }
}

Você criou esta configuração: insira a descrição da imagem aqui

Cada módulo (folha de papel) recebe o seu próprio copo chamado A. Isso é inútil - você não está realmente organizando seu doce aqui, apenas adicionando uma etapa adicional (tirando-a do copo) entre você e as guloseimas.


Versão 2: um copo no escopo global

Se você não estava usando módulos, você pode escrever um código como este (observe a falta de exportdeclarações):

global1.ts

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

global2.ts

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

global3.ts

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

Este código cria um espaço Apara nome mesclado no escopo global:

insira a descrição da imagem aqui

Essa configuração é útil, mas não se aplica aos módulos (porque os módulos não poluem o escopo global).


Versão 3: Indispensável

Voltando ao exemplo original, os copos A, Ae Anão está fazendo nenhum favor. Em vez disso, você pode escrever o código como:

Mod1.ts

export class Twix { ... }

Mod2.ts

export class PeanutButterCup { ... }

Mod3.ts

export class KitKat { ... }

para criar uma imagem parecida com esta:

insira a descrição da imagem aqui

Muito melhor!

Agora, se você ainda está pensando em quanto realmente deseja usar o espaço para nome com seus módulos, continue lendo ...


Estes não são os conceitos que você está procurando

Precisamos voltar às origens do porque os namespaces existem em primeiro lugar e examinar se esses motivos fazem sentido para módulos externos.

Organização : os namespaces são úteis para agrupar objetos e tipos relacionados à lógica. Por exemplo, em C #, você encontrará todos os tipos de coleção System.Collections. Ao organizar nossos tipos em namespaces hierárquicos, fornecemos uma boa experiência de "descoberta" para usuários desses tipos.

Conflitos de nome : os espaços para nome são importantes para evitar colisões de nomes. Por exemplo, você pode ter My.Application.Customer.AddForme My.Application.Order.AddForm- dois tipos com o mesmo nome, mas um espaço para nome diferente. Em um idioma em que todos os identificadores existem no mesmo escopo raiz e todos os assemblies carregam todos os tipos, é essencial ter tudo em um espaço para nome.

Esses motivos fazem sentido em módulos externos?

Organização : Módulos externos já estão presentes em um sistema de arquivos, necessariamente. Temos que resolvê-los por caminho e nome de arquivo, para que exista um esquema de organização lógica para usar. Podemos ter uma /collections/generic/pasta com um listmódulo.

Conflitos de nome : isso não se aplica em todos os módulos externos. Dentro de um módulo, não há razão plausível para ter dois objetos com o mesmo nome. Do lado do consumo, o consumidor de qualquer módulo pode escolher o nome que usará para se referir ao módulo, portanto, conflitos de nomes acidentais são impossíveis.


Mesmo que você não acredite que esses motivos sejam tratados adequadamente pela maneira como os módulos funcionam, a "solução" de tentar usar espaços para nome em módulos externos nem funciona.

Caixas em Caixas em Caixas

Uma história:

Seu amigo Bob liga para você. "Eu tenho um ótimo novo esquema de organização em minha casa", diz ele, "venha conferir!". Legal, vamos ver o que Bob inventou.

Você começa na cozinha e abre a despensa. Existem 60 caixas diferentes, cada uma rotulada como "Despensa". Você escolhe uma caixa aleatoriamente e abre-a. Dentro, há uma única caixa chamada "Grãos". Você abre a caixa "Grãos" e encontra uma única caixa chamada "Macarrão". Você abre a caixa "Pasta" e encontra uma única caixa chamada "Penne". Você abre esta caixa e encontra, como esperado, um saco de macarrão penne.

Um pouco confuso, você pega uma caixa adjacente, também chamada de "Despensa". Dentro, há uma única caixa, novamente rotulada como "Grãos". Você abre a caixa "Grãos" e, novamente, encontra uma única caixa chamada "Macarrão". Você abre a caixa "Pasta" e encontra uma única caixa, esta é rotulada "Rigatoni". Você abre esta caixa e encontra ... um saco de macarrão rigatoni.

"É ótimo!" diz Bob. "Tudo está em um espaço para nome!".

"Mas Bob ..." você responde. "O esquema da sua organização é inútil. Você precisa abrir um monte de caixas para chegar a qualquer coisa, e não é realmente mais conveniente encontrar alguma coisa do que se você tivesse colocado tudo em uma caixa em vez de três . De fato, desde o seu a despensa já está classificada de prateleira a prateleira, você não precisa das caixas. Por que não colocar a massa na prateleira e buscá-la quando precisar? "

"Você não entende - preciso garantir que ninguém mais coloque algo que não pertença ao espaço para nome 'Despensa'. E organizei com segurança toda a minha massa no Pantry.Grains.Pastaespaço para nome, para que eu possa encontrá-lo facilmente"

Bob é um homem muito confuso.

Os módulos são sua própria caixa

Você provavelmente teve algo semelhante acontecendo na vida real: você pede algumas coisas na Amazon e cada item aparece em sua própria caixa, com uma caixa menor dentro, com o item embrulhado em sua própria embalagem. Mesmo se as caixas internas forem semelhantes, as remessas não serão úteis "combinadas".

Seguindo a analogia da caixa, a principal observação é que os módulos externos são sua própria caixa . Pode ser um item muito complexo, com muitas funcionalidades, mas qualquer módulo externo é sua própria caixa.


Orientação para módulos externos

Agora que descobrimos que não precisamos usar 'namespaces', como devemos organizar nossos módulos? Seguem alguns princípios e exemplos orientadores.

Exporte o mais próximo possível do nível superior

  • Se você estiver exportando apenas uma única classe ou função, 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());

Isso é ideal para os consumidores. Eles podem nomear seu tipo como quiserem ( tnesse caso) e não precisam fazer pontuações estranhas para encontrar seus objetos.

  • Se você estiver exportando vários objetos, coloque-os todos no nível superior:

MyThings.ts

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

Consumo

import * as m from './MyThings';
var x = new m.SomeType();
var y = m.someFunc();
  • Se você estiver exportando um grande número de coisas, só então use a palavra-chave 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();

Bandeiras vermelhas

Todos os seguintes são sinais de alerta para a estruturação do módulo. Verifique se você não está tentando nomear seus módulos externos, se algum deles se aplicar aos seus arquivos:

  • Um arquivo cuja única declaração de nível superior é export module Foo { ... }(remova Fooe mova tudo 'para cima' em um nível)
  • Um arquivo que possui um único export classou export functionque não éexport default
  • Vários arquivos com o mesmo export module Foo {nível superior (não pense que eles serão combinados em um Foo!)
Ryan Cavanaugh
fonte
80
Esta é uma não resposta. A premissa de que você não deve precisar ou desejar namespaces para módulos externos é uma falha. Enquanto o sistema de arquivos é uma espécie de esquema de organização que você pode meio que usa para esses fins, não é quase tão bom para o consumidor ter n declarações de importação para usando n classes ou funções de um determinado projeto; especialmente porque também atrapalha a convenção de nomenclatura quando você está no código real.
Albinofrenchy
12
Não importa quanto alguém queira, ainda não é possível .
Ryan Cavanaugh
26
Não entendo, não estamos mais escrevendo pascal. Desde quando a organização usando o sistema de arquivos é o caminho a percorrer?
David David
9
Você pode ter um módulo "wrapper" que importe e reexporte tudo de interesse para os consumidores da sua biblioteca. Mas, novamente, usar um "espaço para nome" não fornecerá outro valor além de forçar outro nível de indireção para quem estiver usando seu código.
precisa
13
Ótimo artigo, obrigado. Eu acho que você deveria criar um link para isso em www.typescriptlang.org/docs/handbook/namespaces.html. Devo ter lido esse link typescriptlang.org 3 ou 4 vezes e, como C # dev, naturalmente quero colocar tudo em um espaço para nome. Eu li algumas sugestões dizendo que não, mas sem nenhuma explicação do porquê e nada tão definitivo (e bem descrito) como este. Além disso, nada nos docs typescript menciona este AFAIK
Adam Plocher
53

Nada de errado com a resposta de Ryan, mas para as pessoas que vieram aqui procurando como manter uma estrutura de uma classe por arquivo enquanto ainda usam os espaços para nome ES6 corretamente, consulte este recurso útil da Microsoft.

Uma coisa que não está clara para mim depois de ler o documento é: como importar o módulo inteiro (mesclado) com um único import .

Edite Circulando de volta para atualizar esta resposta. Algumas abordagens para namespacing surgem no TS.

Todas as classes de módulo em um arquivo.

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

Importar arquivos para o espaço para nome e reatribuir

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

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

Barris

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

Uma consideração final. Você pode namespace cada arquivo

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

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

Mas, como se importa duas classes do mesmo espaço para nome, o TS reclama que há um identificador duplicado. A única solução, neste momento, é alias do espaço para nome.

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

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

Esse apelido é absolutamente repugnante, então não faça isso. Você está melhor com uma abordagem acima. Pessoalmente, eu prefiro o 'barril'.

Jefftopia
fonte
6
O que são "namespaces ES6"?
Aluan Haddad
@AluanHaddad ao importar o es2015 +, os itens importados são padrão, destruídos ou espaçados. const fs = require('fs'), fsé o espaço para nome. import * as moment from 'moment', momenté o espaço para nome. Isso é ontologia, não a especificação.
precisa saber é o seguinte
Estou ciente disso, mas você faria bem em explicá-lo em sua resposta. Os namespaces ES6 são realmente uma coisa, no entanto, e o requireexemplo não se aplica a eles por vários motivos, incluindo que os namespaces ES6 não podem ser chamados, enquanto requireretorna um objeto simples que pode ser chamado .
Aluan Haddad
1
Eu não sigo, porque se a coisa importada é passível de chamar ou não, ainda serve como um espaço para nome logicamente falando. Não acho que as advertências sejam relevantes para a minha resposta acima.
9118 Jefftopia
7

Tente organizar por pasta:

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)

A idéia é que seu próprio módulo não se importe / saiba que está participando de um espaço para nome, mas isso expõe sua API ao consumidor de uma maneira compacta e sensível, independente do tipo de sistema de módulo que você está usando para o projeto.

Albinofrenchy
fonte
8
LivingThings.dog.Dog é o que você tem aqui.
Corey Alix
Eu recomendo manter a letra maiúscula consistente, se você exportar "Árvore" e depois importar "Árvore", não "árvore".
demisx
1
Além disso, como você pode importar algo tree.tsquando ele não possui nenhum membro exportado?
demisx
Man TS certeza tem alguma sintaxe velho bobo, como importe requirejuntos em um comunicado.
Andy
3

Pequeno impulso da resposta 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);
Mike Vitik
fonte
2
Obrigado por isso! Só queria dizer que as alterações em uma resposta existente não devem ser postadas como novas respostas: elas devem ser adicionadas como um comentário à resposta existente ou (melhor) devem ser sugeridas sugerindo uma edição da resposta que você deseja melhorar.
a3nm
3

OP estou com você, cara. novamente, também, não há nada errado com essa resposta com mais de 300 votos, mas minha opinião é:

  1. o que há de errado em colocar as classes em seus próprios arquivos aconchegantes e aconchegantes individualmente? Quero dizer, isso fará com que as coisas pareçam muito melhores, certo? (ou alguém como um arquivo de 1000 linhas para todos os modelos)

  2. então, se o primeiro for alcançado, temos que importar import import ... import apenas em cada um dos arquivos de modelo, como man, srsly, um arquivo de modelo, um arquivo .d.ts, por que existem tantos * está aí? deve ser simples, arrumado e é isso. Por que preciso de importações para lá? porque? C # tem namespaces por um motivo.

  3. E então, você está literalmente usando "filenames.ts" como identificadores. Como identificadores ... Vamos no 2017 agora e ainda fazemos isso? Vou voltar a Marte e dormir por mais 1000 anos.

Por isso, infelizmente, minha resposta é: não, você não pode tornar funcional o "namespace" se não usar todas essas importações ou usar esses nomes de arquivos como identificadores (o que eu acho realmente tolo). Outra opção é: coloque todas essas dependências em uma caixa chamada filenameasidentifier.ts e use

export namespace(or module) boxInBox {} .

envolva-os para que eles não tentem acessar outras classes com o mesmo nome quando estiverem apenas tentando obter uma referência da classe, bem em cima deles.

NÃO ... Bugs ...
fonte
3

Várias das perguntas / comentários que eu vi sobre esse assunto me parecem como se a pessoa estivesse usando o Namespaceque significa 'apelido de módulo'. Como Ryan Cavanaugh mencionou em um de seus comentários, você pode ter um módulo 'Wrapper' reexportando vários módulos.

Se você realmente deseja importar tudo do mesmo nome / alias do módulo, combine um módulo wrapper com um mapeamento de caminhos no seu tsconfig.json.

Exemplo:

./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 : A resolução do módulo nos arquivos .js de saída precisará ser tratada de alguma forma, como neste https://github.com/tleunen/babel-plugin-module-resolver

Exemplo .babelrcpara lidar com a resolução de alias:

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

Experimente este módulo de namespaces

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 da compilação ---

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

A maneira correta de organizar seu código é usar diretórios separados no lugar de namespaces. Cada classe estará em seu próprio arquivo, na respectiva pasta de namespace. index.ts somente reexporta cada arquivo; nenhum código real deve estar no arquivo index.ts. Organizar seu código assim facilita muito a navegação e é auto-documentado com base na estrutura de diretórios.

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

Você usaria então:

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
fonte
Isso é enganoso e absolutamente incorreto. Não é assim que os namespaces funcionam. Também não responde à pergunta das operações.
AndrewMcLagan