Modul vs Namnrymd - Import vs Require Typescript
Jag får mycket förvirring med module/namespace/export och import, require, reference-användning. Eftersom jag har en bakgrund i Java, kan någon förklara för mig i korthet när man ska använda vad och vad som är rätt design? Jag känner att jag gör fel när jag skriver ett exempelt projekt
Hittills är detta min förståelse 1. module är för externa paket 2. namespace är för interna paket
- Jag förstod inte hur vi kategoriserar dem?
- När ska man exportera en klass, ett namnrymd eller ett paket?
- Om vi exporterar paket/namnrymd, exporteras alla klasser inom det eller behöver exporteras uttryckligen
- Hur kan var och en av dem importeras/require?
Enligt dokumentationen, om jag skapar varje "ts"-fil för varje chef/modell, rekommenderar Typescript inte att använda "namespaces"? Ska jag direkt använda referensvägar?
Vänligen förklara i detalj eftersom jag kommer från en annan bakgrund och inte är säker på ES6/ES5 etc.
Jag har sett flera personer som blir förvirrade med samma frågor. Jag hoppas att någon kan förklara i detalj med ett verkligt scenario.
Svar #1
Jag förstod inte hur vi kategoriserar dem?
Namnområden används för att organisera/inkapsla din kod. Externa moduler används för att organisera/inkapsla din kod OCH för att lokalisera din kod vid körning. I praktiken har du två val vid körning: 1) kombinera all transpilad kod i en fil, eller 2) använda externa moduler och ha flera filer och kräva någon annan mekanism för att komma åt dessa filer.
När ska man exportera en klass eller ett namnrymd eller paket?
För att göra en typ eller ett värde synligt utanför filen som det finns i, måste du exportera det om det är inom ett namnrymd. Om du exporterar det på topnivå eller inom en namnrymd kommer att avgöra om det nu är i en extern modul.
Om vi exporterar paket/namnrymd, exporteras alla klasser inom den eller måste explicit exporteras
Klasser i ett namnrymd måste alltid uttryckligen exporteras för att klassen ska vara synlig vid kompileringstid utanför filen där den är definierad.
Hur kan var och en av dem importeras/krävas?
Detta beror på om du använder externa moduler. En extern modul måste alltid importeras för att "användas". Att importera ett namnrymd som inte finns i en extern modul är egentligen bara att tillhandahålla ett alias för namnrymden - du måste fortfarande prefixa typen/vad det än är med aliaset (och detta är anledningen till att man generellt inte vill använda namnrymder med externa moduler; att göra det innebär att du alltid måste använda ett prefix när du refererar till något som tillhandahålls av den externa modulen.) Namnrymder som inte finns i en extern modul kan sträcka sig över filer, så om du är i samma namnrymd kan du referera till allt som exporteras av namnrymden utan att behöva någon form av import.
För att verkligen förstå det ovanstående behöver du lite bakgrundskunskap. Den viktigaste saken att förstå med referenser/namnrymder/externa moduler är vad dessa konstruktioner gör vid kompileringstid och vad de gör vid körtid.
Referensdirektiv används vid kompileringstid för att hitta typinformation. Din källa har en viss symbol i sig. Hur hittar TypeScript-kompilatorn definitionen för den symbolen? Referensdirektivet har till stor del ersatts av tsconfig.json-mekanismen -- genom att använda tsconfig.json berättar du för kompilatorn var alla dina källor finns.
Namnområden kan innehålla typdefinitioner och/eller implementation. Om ett namnområde endast innehåller typinformation så har det ingen runtime-manifestation alls - du kan kontrollera detta genom att titta på JS-utdata och hitta en tom JS-fil. Om ett namnområde har implementationskod, så är koden omsluten av en closure som tilldelas en global variabel med samma namn som namnområdet. Med inbäddade namnområden kommer det att finnas en global variabel för rot-namnområdet. Återigen, kontrollera JS-utdata. Namnområden är historiskt sett hur JS-klientsidans bibliotek har försökt undvika problemet med namnkollisioner. Idén är att omsluta hela ditt bibliotek i en closure och sedan exponera så liten global påverkan som möjligt - bara en global variabel som refererar till closuren. Tja, problemet är fortfarande att du har hävdat ett namn i det globala utrymmet. Tänk om du ville ha, säg, två versioner av ett bibliotek? Ett TypeScript-namespace har fortfarande problemet med hur man hittar källan för namnområdet. Det vill säga, källkoden som refererar till A.B har fortfarande problemet med att tala om för kompilatorn hur man hittar A.B -- antingen genom att använda referensdirektiv eller genom att använda tsconfig.json. Eller genom att lägga namnområdet i en extern modul och sedan importera den externa modulen.
Externa moduler härstammar från server-side JS. Det finns ett ett-till-ett-förhållande mellan en extern modul och en fil på filsystemet. Du kan använda filsystemets katalogstruktur för att organisera externa moduler i en nästlad struktur. Att importera en extern modul kommer generellt alltid att introducera ett körningstidberoende på den externa modulen (undantaget är när du importerar en extern modul men sedan inte använder någon av dess exports i värdepositionen -- det vill säga, du importerar bara den externa modulen för att få åtkomst till dess typinformation). En extern modul är implicit i ett stängningsblock, och detta är nyckeln: användaren av modulen kan tilldela stängningsblocket till vilken lokal variabel de vill. TypeScript/ES6 lägger till ytterligare syntax för att mappa exporterna av de externa modulerna till lokala namn, men detta är bara en bekvämlighet. På serversidan är det relativt enkelt att lokalisera en extern modul: lokalisera bara filen som representerar den externa modulen på det lokala filsystemet. Om du vill använda externa moduler på klientsidan, i en webbläsare, blir det mer komplext eftersom det inte finns någon motsvarighet till filsystemet där modulen är tillgänglig för att laddas. Så nu på klientsidan behöver du ett sätt att paketera alla dessa filer i en form som kan användas fjärrstyrt i webbläsaren -- detta är där modulbuntarverktyg som Webpack (Webpack gör en hel del mer än att bara paketera moduler) och Browserify kommer in i bilden. Modulbuntarverktyg tillåter runtime-upplösning av dina externa moduler i webbläsaren.
Verkligt scenarie: AngularJS. Låt som att externa moduler inte existerar, använd ett enda namnrym för att begränsa nedsmutsning av det globala utrymmet (i exemplet nedan är en enda variabel MyApp allt som finns i det globala utrymmet), exportera endast gränssnitt och använd AngularJS beroendeinjektion för att göra implementationer tillgängliga för användning. Sätt alla klasser i katalogens rot, lägg till en tsconfig.json i roten, installera angularjs-typningar under samma katalogrot så att tsconfig.json också plockar upp dessa, kombinera all output till en JS-fil. Detta fungerar bra för de flesta projekt om återanvändning av kod inte är en stor fråga.
MyService.ts:
namespace MyApp {
// without an export the interface is not visible outside of MyService.ts
export interface MyService {
....
}
// class is not exported; AngularJS DI will wire up the implementation
class MyServiceImpl implements MyService {
}
angular.module("MyApp").service("myService", MyServiceImpl);
}
MyController.ts:
namespace MyApp {
class MyController {
// No import of MyService is needed as we are spanning
// one namespace with multiple files.
// MyService is only used at compile time for type checking.
// AngularJS DI is done on the name of the variable.
constructor(private myService: MyService) {
}
}
angular.module("MyApp").controller("myController", MyController);
}
Använder IIFE för att undvika att förorena den globala körningens scope. I detta exempel skapas inga globala variabler alls. (En tsconfig.json antas.)
Foo.ts:
namespace Foo {
// without an export IFoo is not visible. No JS is generated here
// as we are only defining a type.
export interface IFoo {
x: string;
}
}
interface ITopLevel {
z: string;
}
(function(){
// export required above to make IFoo visible as we are not in the Foo namespace
class Foo1 implements Foo.IFoo {
x: string = "abc";
}
// do something with Foo1 like register it with a DI system
})();
Bar.ts:
// alias import; no external module created
import IFoo = Foo.IFoo;
(function() {
// Namespace Foo is always visible as it was defined at
// top level (outside of any other namespace).
class Bar1 implements Foo.IFoo {
x: string;
}
// equivalent to above
class Bar2 implements IFoo {
x: string;
}
// IToplevel is visible here for the same reason namespace Foo is visible
class MyToplevel implements ITopLevel {
z: string;
}
})();
Genom att använda IIFE kan du eliminera införandet av MyApp som en global variabel i det första exemplet.
MyService.ts:
interface MyService {
....
}
(function() {
class MyServiceImpl implements MyService {
}
angular.module("MyApp").service("myService", MyServiceImpl);
})();
MyController.ts:
(function() {
class MyController {
constructor(private myService: MyService) {
}
}
angular.module("MyApp").controller("myController", MyController);
})();
Svar #2
Det finns två saker:
- Ett modul i TypeScript är en standard-ES6-beteckning, det använder
import/export-nyckelord på topnivå i koden; - Ett namespace är ett begrepp som är specifikt för TypeScript för att hjälpa till att organisera koden på ett föråldrat sätt.
Namnrymder
Det är nästan ett föråldrat begrepp. Före ES6-moduler var det vanliga sättet att separera JavaScript-kod i en webbläsare att skapa globala variabler. Till exempel var alla funktioner i ett API som underscore placerade i en global variabel som hette _.
Detta gamla sätt att gå tillväga är som Java-paket eller PHP-namnrymder. Det är inte anpassat för webben. Den nya ECMAScript-standarden löser problem som: hur använder man två bibliotek som har samma namn? Hur använder man två olika versioner av samma bibliotek?
Meddelande: I versioner av TypeScript före ECMAScript-definitionen av “moduler” (sommar 2014) kallades namespaces för “interna moduler” och moduler kallades för “externa moduler”.
Meddelande 2: Nyckelordet export inne i ett namespace är en icke-standardiserad TypeScript-användning av nyckelordet. Det är sättet att deklarera en sak som är offentligt åtkomlig från utsidan av namespace.
ES6-moduler
Ett modul är en fil som innehåller nyckelorden import eller export på den översta nivån av koden.
TypeScript följer standarden från ECMAScript. Jag föreslår att läsa en bra introduktion till ES6-moduler i en artikel från Mozilla.
Om du vill använda moduler i en front-end-applikation (i en webbläsare), måste du använda en bundler (Webpack [dokumentationen här], Browserify) eller en loader (SystemJS [en handledning här], RequireJS) och konfigurera TypeScript med denna miljö.
Om din kod körs i Node.js, konfigurera bara TypeScript-kompilatorn för att generera CommonJS-formatet.
Meddelande: Ett namnrymd kan deklareras i en modul. I så fall kommer det inte att vara åtkomligt som en global variabel utanför modulen. Det kan dock exporteras från modulen.
Svar #3
- modul är för externa paket 2. namnrymd är för interna paket
Faktiskt har module-nyckelordet ersatts med namespace-nyckelordet.
Ett bättre uttalande är således Moduler är det som tidigare kallades externa moduler, namnrymd är det som tidigare kallades interna moduler.
Mer
Hoppas detta hjälper vidare: https://basarat.gitbooks.io/typescript/content/docs/project/modules.html
