En omfattande guide till ”Module System” i TypeScript (med exempel)
On oktober 16, 2021 by adminModulstandard
EcmaScript har standardiserat hur vi ska importera moduler i JavaScript och denna standard kretsar kring två nyckelord, import
och export
. Eftersom modulsystemet i JavaScript blir alltmer populärt kommer det nya ändringar i denna standard varje år.
I den här handledningen kommer vi att titta på semantiken i modulsystemet i TypeScript (som till största delen liknar EcmaScript-standarden) och hur nyckelordet import
och export
fungerar.
Namngivna exporter
Nyckelordet export
gör ett värde (variabel) som definierats i en fil tillgängligt för import i andra modulfiler. När en modul importeras i en annan modulfil med hjälp av nyckelordet import
kan den importerande filen ange de värden som den vill ha tillgång till från den importerade modulen.
// program.ts
import { A } from 'path/to/values';console.log( A ); // "Apple"
I exemplet ovan importerar vi values.ts
i filen program.ts
och extraherar den exporterade medlemmen A
. Detta innebär att values.ts
måste exportera värdet A
i någon form. Det finns flera sätt att exponera A
.
// values.ts
var A = "Apple"; // can also use `let` or `const`
class B {}
function C(){}
enum D{}export { A, B, C, D };
Du kan exportera alla tillgängliga TypeScript-värden med en giltig identifierare (namn). Här i det här exemplet exporterar vi en variabel A
(kan också vara en konstant), klass B
och så vidare. Med hjälp av export {}
-syntaxen kan du exportera hur många värden som helst och vem som helst kan importera dem med hjälp av import { A, B }
-syntaxen. Du behöver inte importera alla exporterade värden.
Det finns ett annat sätt att exportera ett värde där det deklarerades.
// values.ts
export const A = { name: "Apple" };
export class B {}
export function C(){}
export enum D{}
I exemplet ovan har vi exporterat alla värden där de deklarerades. Du behöver inte heller initialisera värdet, det kan du göra senare i filen. Detta är ett mycket trevligare sätt jämfört med det föregående.
I båda fallen är alla dina exportmedlemmar tillgängliga i samma fil om du behöver använda dem. Att ha nyckelordet export
på ett värde ändrar inte dess beteende inom modulfilen.
Default Export
So långt har vi exporterat värden som bara kan importeras genom att ange namnet på exportmedlemmar som import { A } from 'path/to/values'
där A
här är en exportmedlem av values.ts
. Dessa kallas namngivna exporter.
Du får också exportera ett enda värde som kan importeras utan att ange ett namn. Detta värde ska identifieras med nyckelordet default
tillsammans med nyckelordet export
.
// values.ts
var A = "Apple"; // can also use `let` or `const`
class A {}
function A(){}
enum A{}export default A;
Bara ett enda värde får exporteras som en standardexport, därför har du inget export default {...}
-uttryck att arbeta med. Till skillnad från namngiven export kan ett värde inte exporteras som standard med en deklaration av en variabel (eller konstant) med undantag för en function
– och class
-deklaration.
// values.ts
export default var A = "Apple"; // ❌ invalid syntax
export default enum D{} // ❌ illegal: not a function or class
export default class B {} // ✅ legal
export default function C(){} // ✅ legal
Det går dock att exportera ett värde direkt som en standardexport. Detta innebär att vilket JavaScript-uttryck som helst kan exporteras som standardvärde för export.
// values.ts
export default "Hello"; // string
export default {}; // object
export default () => undefined; // function
export default function(){}; // function
export default class{}; // class
Då standardexport saknar ett exportnamn kan vi ange vilket namn som helst för det vid import. Du kan ha namngivna exporter och en standardexport i samma modulfil. På samma sätt kan du importera värden för standardexport och namngiven export i en enda import
-deklaration som visas nedan.
import Apple, { B, C } from 'path/to/values';
I exemplet ovan är Apple
det namn som vi använde för att samla in standardexporten från values.ts
. Du kan ta bort { B, C }
-uttrycket eller Apple
-namnet från import
-deklarationen om du inte tänker använda det.
💡 Precis som vid namngiven export är det inte obligatoriskt att importera standardexportvärdet när
import
-deklarationen används. MenApple
måste komma före den namngivna exporten iimport
-deklarationens signatur.
Du kan också använda as default
-syntaxen i export {}
-signaturen för att exportera ett värde som standard tillsammans med andra namngivna exporter. Detta kallas aliasing och förklaras i nästa avsnitt.
// values.ts
var A = "Apple"; // can also use `let` or `const`
class B {}
function C(){}
enum D{}export { A as default, B, C, D };
💡 Anledningen till att jag inte använder och rekommenderar
default
export har att göra med utvecklarens erfarenhet. Som vi lärde oss kan du ange vilket namn som helst för en moduls standardexportmedlem iimport
-deklarationen. Om detta värde importeras i flera filer kan du referera till det med valfritt namn och att ha olika namn för samma exportmedlem i samma modul är en dålig DX.
Import And Export Alias
I fallet med standardexporten kan du referera till värdet med valfritt namn i import
-deklarationen. Det är dock inte fallet med namngivna exporter. Du kan dock använda nyckelordet as
för att referera till ett namngivet exportvärde med ett valfritt namn.
// program.ts
import A, { B as Ball } from 'path/to/values';console.log( B ); // ❌ Error: Cannot find name 'B'.
console.log( Ball ); // ✅ legal
I exemplet ovan importerar vi B
från values.ts
men det har bytt namn till Ball
. I program.ts
-filen använder du därför Apple
i stället för B
. När du aliasar en exportmedlem existerar det ursprungliga namnet inte längre i den globala räckvidden, därför kommer B
inte längre att existera i program.ts
.
💡 Du kan inte aliasa standard-exportmedlemmen eftersom du redan kan ange ett valfritt namn, men du kan använda
import { default as defaultValue, }
.
Du får också aliasera ett värde när du exporterar med nyckelordet as
.
// values.ts
var A = "Apple"; // can also use `let` or `const`
class B {}
function C(){}
enum D{}export { A as default, B, C as Cat, D };
I exemplet ovan har vi exporterat variabeln A
som standardexport och funktionen C
som Cat
. Därför måste alla som importerar dessa värden från values.ts
-filen använda syntaxen för standardimport för A
och Cat
för C
-värdet.
Importera alla namngivna exporter
Du kan importera alla namngivna exporter från en fil med hjälp av * as
-syntaxen.
// program.ts
import Apple, * as values from 'path/to/values';console.log( values.B );
console.log( values.C );
console.log( values.D );
Här kommer alla namngivna exporter från values.ts
att sparas under values
som kommer att vara ett objekt där keys
kommer att vara namnet på exportmedlemmarna och values
kommer att vara de exporterade värdena för exportmedlemmarna.
Re-exporterar
Ett importerat värde kan återexporteras på vanligt sätt. När du importerar något har du en referens till importvärdet och du kan använda samma värde i export
-syntaxen. Det är inget fel med det.
// lib.ts
import A, { B, C as Cat, D } from 'path/to/values';export { D as default, A, B, Cat as C, D };
I exemplet ovan har du importerat några värden från values.ts
inuti lib.ts
och exporterat enligt dina önskemål. Detta är bra om du vill använda dessa importer i lib.ts
samt exportera några av dem om en annan fil behöver dem när den importerar från lib.ts
.
Hur som helst har vi också export ... from
-anvisningen för att exportera värden från en fil utan att behöva importera dem först. Detta är bra när du vill ha en enda ingångspunkt för alla importer i din modul.
// lib.ts// re-exports
export { P as Paper, Q } from 'path/to/values-1';
export { N, O as Orange } from 'path/to/other/values-2';// default export
export default "hello";class B {}
function C(){}// named exports
export { B, C as Cat };
I exemplet ovan exporterar vi några av exportmedlemmarna i values-1.ts
och values-2.ts
från lib.ts
-filen (förutom standardexporten). Vi kan också använda aliasing i export ... from
-syntaxen.
Syntaxen för återexport påverkar inte den aktuella filen, därför kan du ha din vanliga export i filen också som visas ovan. Till skillnad från nyckelordet export
tillåter export ... from
-syntaxen inte åtkomst till återexporterade medlemmar. Därför kan du inte komma åt P
eller Orange
i lib.ts
-filen.
// lib.ts
export { P as Paper, Q } from 'path/to/values-1';
export { N, O as Orange } from 'path/to/other/values-2';console.log( P ); // ❌ Error: Cannot find name 'P'.
console.log( Orange ); // ❌ Error: Cannot find name 'Orange'.
Du kan exportera alla namngivna exporter från en fil med hjälp av export * from
-syntaxen så länge du har möjlighet att byta namn på dem när du exporterar med hjälp av export * as ... from
.
// lib.ts
export * from 'path/to/values-1';
export * as values2 from 'path/to/other/values-2';
Från exemplet ovan, om values-1.ts
exporterar P
och Q
, kan vem som helst importera P
och Q
från lib.ts
. Alla namngivna exporter från values-2.ts
återexporteras dock som values2
namngiven export från lib.ts
, och därför måste du importera dem med hjälp av följande syntax:
// program.ts
import { P, Q, values2 } from 'path/to/lib';
Du kan inte få tillgång till standardexport med hjälp av export ... from
-syntaxen på ett normalt sätt. Men du kan exportera standardexporten med nyckelordet default
.
// lib.ts
export { default } from 'path/to/values-1';
export { default as Apple } from 'path/to/values-2';
I exemplet ovan är standardexporten av lib.ts
standardexportvärdet för values-2.ts
. Standardvärdet för export av values-2
kommer att exporteras från lib.ts
som den Apple
namngivna exporten.
Import för sidoeffekt
Kan du tänka dig att importera en fil utan att specificera de exporterade medlemmarna?
import 'path/to/action';
I exemplet ovan importerar vi action.ts
, men vi har specificerat alla medlemmar som exporteras av action.ts
. Detta är en giltig syntax men den används för att ge ett helt annat resultat.
När du importerar en fil med hjälp av nyckelordet import
exekveras det först och sedan är alla exportmedlemmar tillgängliga för import. I exemplet nedan skulle därför en fil som importerar PI
få 3.14
som ett floating-point
-nummer.
export const PI = parseFloat( "3.14" ); // 3.14
Om du vill skapa en sidoeffekt genom att importera en fil kan du helt enkelt göra det enligt samma logik. Så om det till exempel finns en fil som initialiserar några globala variabler kan du importera den utan att ange de exporterade medlemmarna i den filen (den kan också exportera inga i första hand).
TypeScript-kompilatorn kan skapa en JavaScript-bundle-fil (.js
) genom att kompilera två eller flera .ts
-filer tillsammans. Detta görs normalt genom att tillhandahålla en Entry TypeScript-fil och sedan gå igenom dess beroendeträd.
Ett beroendeträd konstrueras genom att titta på alla import
-deklarationer (beroenden) för Entry-filen och följa beroendena för dessa importerade filer. Om du vill inkludera en fil vars kod kan skapa vissa sidoeffekter vid körning bör du importera den filen utan att ange de exporterade medlemmarna i import
-syntaxen. Detta kan göras i inmatningsfilen eller i vilken fil som helst som finns i beroendeträdet.
Lämna ett svar