módulo - Exportar uma classe TypeScript de outro arquivo diferente daquele em que foi definida
Há algum tempo venho tentando exportar as classes de uma pequena biblioteca que escrevi em um único lugar. Quero que cada classe seja agnóstica quanto a como elas estão disponíveis para o exterior, mas até agora não consegui fazer isso funcionar.
Vamos supor que eu tenha a classe Foo
// lib/foo.ts
export class Foo {}
e a classe Bar
// lib/bar.ts
export class Bar {}
Agora eu quero tornar esses disponíveis para quem usar o pacote via o namespace MyModule e é aqui que fica complicado. Eu não sei como exportar classes existentes de um namespace definido em outro lugar que não seja a própria classe.
Obviamente, isso não vai funcionar porque não é TypeScript válido:
// api.ts
import { Foo } from "./lib/foo";
import { Bar } from "./lib/bar";
export namespace MyModule {
export Foo;
export namespace MySubModule {
export Bar;
}
}
Encontrei duas soluções potenciais para isso, mas ambas têm suas desvantagens. Posso definir uma variável e atribuir a classe a ela:
// api.ts
import { Foo as MyFoo } from "./lib/foo";
import { Bar as MyBar } from "./lib/bar";
export namespace MyModule {
export const Foo = MyFoo;
export namespace MySubModule {
export const Bar = MyBar;
}
}
Isso, de fato, me permitirá criar instâncias da classe a partir de um local externo como new MyModule.MySubModule.Bar, mas não me dará a possibilidade de usá-las como tipos, pois
let myBarInstance: MyModule.MySubModule.Bar;
gerará um erro do TypeScript indicando que
Módulo "path/to/module/api".MySubModule não possui um membro exportado 'Bar'
Do outro lado, eu tentei usar export type Bar=MyBar em vez de const e, embora isso funcione ao dizer
let myBarInstance: MyModule.MySubModule.Bar
por outro lado, eu naturalmente não posso instanciar MyModule.MySubModule.Bar já que é apenas um alias de tipo.
Tudo isso funciona da mesma maneira para a classe Foo.
Alguém tem alguma experiência com isso, eu talvez tenha esquecido algum outro recurso de exportação que o TypeScript oferece?
Resposta #1
Então Ryan Cavanaugh, da equipe do TypeScript, foi gentil o suficiente para responder à minha pergunta no GitHub deles.
Na verdade, é bem fácil: basta combinar as duas abordagens que eu criei e você estará pronto para começar:
// api.ts
import { Foo as MyFoo } from "./lib/foo";
import { Bar as MyBar } from "./lib/bar";
export namespace MyModule {
export const Foo = MyFoo;
export type Foo = MyFoo;
export namespace MySubModule {
export const Bar = MyBar;
export type Bar = MyBar;
}
}
EDIT 2020: Agora que tenho alguns anos a mais de experiência com TypeScript, acho que devo fornecer uma explicação adicional por que o código acima funciona. Isso pode ser uma informação valiosa para desenvolvedores relativamente novos em TypeScript, porque aborda um tópico que nem sempre é ensinado: o escopo de tipo separado do TypeScript.
O TypeScript basicamente mantém um escopo de tipos paralelo ao escopo de variáveis do JavaScript. Isso significa que você pode definir uma variável foo e um tipo foo no mesmo arquivo. Eles nem precisam ser compatíveis:
const foo = 'bar'
type foo = number
Agora, classes em TypeScript são um pouco especiais. O que acontece se você definir uma classe Foo é que o TypeScript não apenas cria uma variável Foo (contendo o próprio objeto da classe), ele também declara um tipo Foo, representando uma instância dessa classe.
Da mesma forma, ao importar um nome de outro arquivo, ambos — qualquer variável e tipo definidos sob esse nome — são importados. Isso significa que no código acima, MyFoo contém o objeto da classe Foo assim como o tipo da classe Foo de foo.ts.
Então, se reexportarmos MyFoo de dentro do namespace MyModule escrevendo export const Foo=MyFoo, apenas o objeto da classe é exportado (porque um const sempre contém apenas um valor, não um tipo). Da mesma forma, se fizermos export type Foo=MyFoo, apenas o tipo da classe seria exportado.
Então agora chegamos ao ponto inicial: Por causa dos escopos independentes, é válido exportar um valor e um tipo com o mesmo nome. E, em alguns casos (como este), não é apenas válido, mas necessário.
