Jak korzystać z przestrzeni nazw z modułami zewnętrznymi TypeScript?

233

Mam trochę kodu:

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 {

  }
}

To wszystko jest bardzo mylące. Chcę mieć kilka modułów zewnętrznych przyczyniają typy do tej samej przestrzeni nazw, Living.Things. Wydaje się, że to nie działa w ogóle - nie widzę Animalw dogs.ts. Muszę napisać pełną nazwę przestrzeni nazw b.Living.Things.Plantw tree.ts. Nie działa łączenie wielu obiektów w tej samej przestrzeni nazw w pliku. Jak mam to zrobic?

Ryan Cavanaugh
źródło

Odpowiedzi:

860

Analogia Candy Cup

Wersja 1: Kubek na każdy cukierek

Powiedzmy, że napisałeś taki kod:

Mod1.ts

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

Mod2.ts

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

Mod3.ts

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

Utworzyłeś tę konfigurację: wprowadź opis zdjęcia tutaj

Każdy moduł (arkusz papieru) otrzymuje własną filiżankę o nazwie A. Jest to bezużyteczne - tak naprawdę nie organizujesz tutaj swojego cukierka, po prostu dodajesz dodatkowy krok (wyjmując go z kubka) między tobą a smakołykami.


Wersja 2: Jeden kubek w zasięgu globalnym

Jeśli nie korzystasz z modułów, możesz napisać taki kod (zwróć uwagę na brak exportdeklaracji):

global1.ts

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

global2.ts

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

global3.ts

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

Ten kod tworzy scaloną przestrzeń nazw Aw zasięgu globalnym:

wprowadź opis zdjęcia tutaj

Ta konfiguracja jest przydatna, ale nie ma zastosowania w przypadku modułów (ponieważ moduły nie zanieczyszczają zasięgu globalnego).


Wersja 3: Going cupless

Wracając do pierwotnego przykład kubki A, Ai Anie robią ci żadnych przysług. Zamiast tego możesz napisać kod jako:

Mod1.ts

export class Twix { ... }

Mod2.ts

export class PeanutButterCup { ... }

Mod3.ts

export class KitKat { ... }

aby utworzyć zdjęcie, które wygląda następująco:

wprowadź opis zdjęcia tutaj

Dużo lepiej!

Jeśli nadal zastanawiasz się, jak bardzo chcesz używać przestrzeni nazw w swoich modułach, czytaj dalej ...


To nie są koncepcje, których szukasz

Musimy przede wszystkim wrócić do początków istnienia obszarów nazw i zbadać, czy te przyczyny mają sens w przypadku modułów zewnętrznych.

Organizacja : Przestrzenie nazw są przydatne do grupowania logicznie powiązanych obiektów i typów. Na przykład w języku C # znajdziesz wszystkie typy kolekcji System.Collections. Organizując nasze typy w hierarchiczne przestrzenie nazw, zapewniamy dobre wrażenia „odkrywcze” użytkownikom tych typów.

Konflikty nazw: Przestrzenie nazw są ważne, aby uniknąć kolizji nazw. Na przykład możesz mieć My.Application.Customer.AddFormi My.Application.Order.AddForm- dwa typy o tej samej nazwie, ale o innej przestrzeni nazw. W języku, w którym wszystkie identyfikatory istnieją w tym samym zakresie głównym, a wszystkie zestawy ładują wszystkie typy, bardzo ważne jest, aby wszystko było w przestrzeni nazw.

Czy te powody mają sens w modułach zewnętrznych?

Organizacja : Moduły zewnętrzne są już koniecznie obecne w systemie plików. Musimy je rozwiązać według ścieżki i nazwy pliku, więc mamy do dyspozycji logiczny schemat organizacyjny. Możemy mieć /collections/generic/folder z listmodułem.

Konflikty nazw : To nie dotyczy w ogóle modułów zewnętrznych. W module nie ma żadnego uzasadnionego powodu, aby mieć dwa obiekty o tej samej nazwie. Od strony konsumpcji konsument dowolnego modułu może wybrać nazwę, której będzie używał w odniesieniu do modułu, więc przypadkowe konflikty nazw są niemożliwe.


Nawet jeśli nie wierzysz, że przyczyny te są odpowiednio rozwiązane przez sposób działania modułów, „rozwiązanie” próby użycia przestrzeni nazw w modułach zewnętrznych nawet nie działa.

Pudełka w Pudełka w Pudełkach

Historia:

Twój przyjaciel Bob dzwoni do ciebie. „Mam świetny nowy program organizacyjny w moim domu”, mówi, „chodź, sprawdź to!”. Schludnie, zobaczmy, co wymyślił Bob.

Zaczynasz w kuchni i otwierasz spiżarnię. Istnieje 60 różnych pudełek, każde oznaczone jako „Spiżarnia”. Losowo wybierasz pudełko i je otwierasz. Wewnątrz znajduje się pojedyncze pudełko z napisem „Ziarna”. Otworzysz okno „Ziarna” i znajdziesz pojedyncze pudełko z etykietą „Makaron”. Otworzysz pudełko „Makaron” i znajdziesz pojedyncze pudełko z napisem „Penne”. Otworzysz to pudełko i znajdziesz, jak się spodziewasz, torebkę makaronu penne.

Nieco zdezorientowany podnosisz sąsiednie pudełko, oznaczone również jako „Spiżarnia”. Wewnątrz znajduje się pojedyncze pudełko, ponownie oznaczone jako „Ziarna”. Otworzysz okno „Ziarna” i ponownie znajdziesz pojedyncze pudełko z etykietą „Makaron”. Otworzysz pudełko „Makaron” i znajdziesz jedno pudełko, które ma etykietę „Rigatoni”. Otwierasz to pudełko i znajdujesz ... torebkę makaronu rigatoni.

"Wspaniale!" mówi Bob. „Wszystko jest w przestrzeni nazw!”.

„Ale Bob ...” odpowiadasz. „Twój schemat organizacyjny jest bezużyteczny. Musisz otworzyć kilka pudełek, aby dostać się do czegokolwiek, a znalezienie czegoś nie jest wygodniejsze, niż gdybyś po prostu umieścił wszystko w jednym pudełku zamiast trzech . W rzeczywistości, ponieważ spiżarnia jest już posortowana półka po półce, w ogóle nie potrzebujesz pudełek. Dlaczego nie po prostu położyć makaronu na półce i podnieść go, gdy jest potrzebny? ”

„Nie rozumiesz - muszę się upewnić, że nikt inny nie umieści czegoś, co nie należy do przestrzeni nazw„ Spiżarnia ”. I bezpiecznie zorganizowałem cały mój makaron w Pantry.Grains.Pastaprzestrzeni nazw, aby łatwo go znaleźć”

Bob jest bardzo zdezorientowanym mężczyzną.

Moduły są ich własnym pudełkiem

Prawdopodobnie zdarzyło Ci się coś podobnego w prawdziwym życiu: zamawiasz kilka rzeczy na Amazon, a każdy przedmiot pojawia się we własnym pudełku, z mniejszym pudełkiem w środku, z przedmiotem zapakowanym we własne opakowanie. Nawet jeśli pudełka wewnętrzne są podobne, przesyłki nie są użytecznie „łączone”.

Idąc za analogią skrzynki, kluczową obserwacją jest to, że moduły zewnętrzne są ich własną skrzynką . Może to być bardzo złożony przedmiot z dużą ilością funkcji, ale każdy dany moduł zewnętrzny jest własnym pudełkiem.


Wskazówki dotyczące modułów zewnętrznych

Teraz, gdy zorientowaliśmy się, że nie musimy używać „przestrzeni nazw”, w jaki sposób powinniśmy organizować nasze moduły? Poniżej przedstawiono niektóre zasady przewodnie i przykłady.

Eksportuj tak blisko najwyższego poziomu, jak to możliwe

  • Jeśli eksportujesz tylko jedną klasę lub funkcję, użyj export default:

MyClass.ts

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

MyFunc.ts

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

Konsumpcja

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

Jest to optymalne dla konsumentów. Mogą nazwać twój typ, jak chcą ( tw tym przypadku) i nie muszą wykonywać żadnych dodatkowych kropek, aby znaleźć twoje obiekty.

  • Jeśli eksportujesz wiele obiektów, umieść je wszystkie na najwyższym poziomie:

MyThings.ts

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

Konsumpcja

import * as m from './MyThings';
var x = new m.SomeType();
var y = m.someFunc();
  • Jeśli eksportujesz dużą liczbę rzeczy, tylko wtedy powinieneś użyć słowa kluczowego module/ namespace:

MyLargeModule.ts

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

Konsumpcja

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

Czerwone flagi

Wszystkie poniższe są czerwonymi flagami dla struktury modułu. Sprawdź dokładnie, czy nie próbujesz przestrzeni nazw zewnętrznych modułów, jeśli któryś z nich dotyczy twoich plików:

  • Plik, którego jedyną deklaracją najwyższego poziomu jest export module Foo { ... }(usuń Fooi przenieś wszystko „w górę” o poziom)
  • Plik, który ma jeden export classlub export functionktóry nie jestexport default
  • Wiele plików, które mają to samo export module Foo {na najwyższym poziomie (nie myśl, że zostaną połączone w jeden Foo!)
Ryan Cavanaugh
źródło
80
To nie jest odpowiedź. Założenie, że nie powinieneś potrzebować ani nie chcieć przestrzeni nazw dla modułów zewnętrznych, jest wadliwe. Podczas gdy system plików jest rodzajem schematu organizacyjnego można trochę wykorzystać do tych celów, to nie jest tak miły dla konsumentów mają n instrukcje importu dla klasy N lub za pomocą funkcji z danego projektu; zwłaszcza, że ​​zamazuje również konwencję nazewnictwa, kiedy nie ma się kodu.
Albinofrenchy,
12
Bez względu na to, jak bardzo można tego chcieć, nadal nie jest to możliwe .
Ryan Cavanaugh,
26
Nie rozumiem, nie piszemy już Pascala. Od kiedy organizowanie za pomocą systemu plików jest właściwą drogą?
David
9
Możesz mieć moduł „opakowania”, który importuje i ponownie eksportuje wszystko, co jest interesujące dla konsumentów Twojej biblioteki. Ale znowu, używając „przestrzeni nazw”, nie zapewnisz żadnej wartości poza wymuszeniem innego poziomu pośrednictwa dla każdego, kto używa twojego kodu.
Ryan Cavanaugh
13
Świetne napisanie, dziękuję. Wydaje mi się, że powinieneś link do tego na stronie www.typescriptlang.org/docs/handbook/namespaces.html. Musiałem przeczytać ten link typecriptlang.org 3 lub 4 razy i jako twórca C #, naturalnie chcę umieścić wszystko w przestrzeni nazw. Przeczytałem kilka sugestii mówiących, żeby tego nie robić, ale bez wyjaśnienia, dlaczego i nic tak definitywnego (i dobrze opisanego) jak to. Poza tym nic w dokumentach maszynopisu nie wspomina o tym AFAIK
Adam Plocher
53

Nie ma nic złego w odpowiedzi Ryana, ale dla osób, które tu przyjechały i szukają sposobu na utrzymanie struktury jednej klasy na plik przy jednoczesnym prawidłowym korzystaniu z przestrzeni nazw ES6, zapoznaj się z tym pomocnym zasobem firmy Microsoft.

Jedną z rzeczy, które są dla mnie niejasne po przeczytaniu dokumentu, jest: jak zaimportować cały (scalony) moduł za pomocą jednego import .

Edytuj Krążenie w tył, aby zaktualizować tę odpowiedź. Kilka podejść do przestrzeni nazw pojawia się w TS.

Wszystkie klasy modułów w jednym pliku.

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

Zaimportuj pliki do przestrzeni nazw i ponownie przypisz

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

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

Beczki

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

Ostateczne rozważanie. Państwo mogłoby przestrzeni nazw każdego pliku

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

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

Ale ponieważ jedna importuje dwie klasy z tej samej przestrzeni nazw, TS skarży się, że istnieje duplikat identyfikatora. Jedynym rozwiązaniem, ponieważ tym razem jest alias przestrzeni nazw.

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

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

To aliasing jest absolutnie odrażające, więc nie rób tego. Lepiej jest z powyższym podejściem. Osobiście wolę „beczkę”.

Jefftopia
źródło
6
Co to są „przestrzenie nazw ES6”?
Aluan Haddad
@AluanHaddad podczas importowania es2015 + rzeczy importowane są domyślnie, zniszczone lub mają przestrzeń nazw. const fs = require('fs'), fsto przestrzeń nazw. import * as moment from 'moment', momentto przestrzeń nazw. To jest ontologia, a nie specyfikacja.
Jefftopia,
Jestem tego świadomy, ale dobrze byś to wyjaśnił w swojej odpowiedzi. Przestrzenie nazw ES6 są jednak w rzeczywistości rzeczą i requireprzykład nie ma do nich zastosowania z wielu powodów, w tym dlatego, że przestrzenie nazw ES6 nie mogą być wywoływane, a requirezwraca zwykły obiekt, który może być wywoływalny.
Aluan Haddad,
1
Nie podążam, ponieważ niezależnie od tego, czy importowana rzecz jest możliwa do wywołania, czy też nie, logicznie rzecz biorąc, służy ona jako przestrzeń nazw . Nie sądzę, by zastrzeżenia były istotne dla mojej powyższej odpowiedzi.
Jefftopia,
7

Spróbuj uporządkować według folderów:

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)

Chodzi o to, że sam moduł nie powinien się przejmować / wiedzieć, że uczestniczy w przestrzeni nazw, ale to ujawnia twoje API konsumentowi w zwarty, rozsądny sposób, który jest niezależny od typu systemu modułów, którego używasz w projekcie.

Albinofrenchy
źródło
8
LivingThings.dog.Dog jest tym, co masz tutaj.
Corey Alix,
Polecam zachować spójność wielkości liter, jeśli eksportujesz „Drzewo”, a następnie importujesz „Drzewo”, a nie „Drzewo”.
demisx
1
Ponadto, w jaki sposób możesz zaimportować cokolwiek, tree.tsskoro nie ma ono w ogóle eksportowanego członka?
demisx
Man TS ma pewną głupią starą składnię, taką jak importi requirerazem w jednym zdaniu.
Andy
3

Małe ulepszenie odpowiedzi 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
źródło
2
Dzięki za to! Chciałem tylko powiedzieć, że zmiany istniejącej odpowiedzi nie powinny być publikowane jako nowe odpowiedzi: należy je dodać jako komentarz do istniejącej odpowiedzi lub (lepiej) zasugerować, sugerując edycję odpowiedzi, którą chcesz ulepszać.
a3nm
3

OP Jestem z tobą stary. znowu, nie ma nic złego w tej odpowiedzi z ponad 300 głosami, ale moim zdaniem jest:

  1. co jest złego w indywidualnym umieszczaniu zajęć w przytulnych, ciepłych plikach? Mam na myśli, że dzięki temu wszystko będzie wyglądać znacznie lepiej, prawda? (lub ktoś taki jak plik linii 1000 dla wszystkich modeli)

  2. więc jeśli pierwszy zostanie osiągnięty, musimy zaimportować import import ... importuj tylko w każdym pliku modelu, takim jak man, srsly, plik modelu, plik .d.ts, dlaczego jest tak wiele * jest tam? powinno być po prostu proste, schludne i to wszystko. Dlaczego potrzebuję tam importu? czemu? C # ma przestrzenie nazw bez powodu.

  3. Do tego czasu dosłownie używasz „filenames.ts” jako identyfikatorów. Jako identyfikatory ... Chodź teraz w 2017 roku i nadal to robimy? Wrócę na Marsa i śpię jeszcze przez 1000 lat.

Niestety moja odpowiedź brzmi: nie, nie możesz sprawić, by „przestrzeń nazw” działała, jeśli nie używasz wszystkich tych importów lub używasz tych nazw plików jako identyfikatorów (co moim zdaniem jest naprawdę głupie). Inną opcją jest: umieść wszystkie te zależności w polu o nazwie filenameasidentifier.ts i użyj

export namespace(or module) boxInBox {} .

owiń je, aby nie próbowały uzyskać dostępu do innych klas o tej samej nazwie, gdy po prostu starają się uzyskać referencję od klasy, usiąść nad nimi.

NIE ... Błędy ...
źródło
3

Kilka pytań / komentarzy, które widziałem wokół tego tematu, brzmi dla mnie tak, jakby osoba używała Namespacetam, gdzie mają na myśli „alias modułu”. Jak wspomniał Ryan Cavanaugh w jednym ze swoich komentarzy, moduł Wrapper może ponownie eksportować kilka modułów.

Jeśli naprawdę chcesz zaimportować wszystko z tej samej nazwy / aliasu modułu, połącz moduł opakowania z mapowaniem ścieżek w swoim tsconfig.json.

Przykład:

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

Uwaga : Rozdzielczość modułu w wyjściowych plikach .js będzie musiała być jakoś obsługiwana, na przykład za pomocą tego https://github.com/tleunen/babel-plugin-module-resolver

Przykład .babelrcobsługi rozdzielczości aliasu:

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

Wypróbuj ten moduł przestrzeni nazw

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

--- część kompilacji ---

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
źródło
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
źródło
-1

Właściwym sposobem organizacji kodu jest użycie oddzielnych katalogów zamiast przestrzeni nazw. Każda klasa będzie we własnym pliku, w odpowiednim folderze przestrzeni nazw. index.ts ponownie wyeksportuje tylko każdy plik; w pliku index.ts nie powinien znajdować się żaden rzeczywisty kod. Zorganizowanie kodu w ten sposób znacznie ułatwia nawigację i samo dokumentuje się na podstawie struktury katalogów.

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

Użyłbyś go jako takiego:

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
źródło
Jest to mylące i absolutnie niepoprawne. Nie tak działają przestrzenie nazw. Również nie odpowiada na pytanie operacyjne.
AndrewMcLagan