Komplexní průvodce „systémem modulů“ v jazyce TypeScript (s příklady)
On 16 října, 2021 by adminStandard modulů
EcmaScript standardizoval způsob importování modulů v jazyce JavaScript a tento standard se točí kolem dvou klíčových slov, import
a export
. Protože se systém modulů v JavaScriptu stává stále populárnějším, přicházejí do tohoto standardu každý rok nové změny.
V tomto kurzu se podíváme na sémantiku systému modulů v jazyce TypeScript (který se z větší části podobá standardu EcmaScript) a na to, jak fungují klíčová slova import
a export
.
Klíčové slovo export
zpřístupňuje hodnotu (proměnnou) definovanou v souboru pro import do jiných souborů modulů. Při importu modulu do jiného souboru modulu pomocí klíčového slova import
může importující soubor určit hodnoty, které chce z importovaného modulu zpřístupnit.
// program.ts
import { A } from 'path/to/values';console.log( A ); // "Apple"
import { A } from 'path/to/values';console.log( A ); // "Apple"
V uvedeném příkladu importujeme values.ts
v souboru program.ts
a extrahujeme exportovaný člen A
. To znamená, že values.ts
musí v nějaké podobě exportovat hodnotu A
. Existuje více způsobů, jak vystavit A
.
// values.ts
var A = "Apple"; // can also use `let` or `const`
class B {}
function C(){}
enum D{}export { A, B, C, D };
Můžete exportovat jakoukoli dostupnou hodnotu jazyka TypeScript s platným identifikátorem (názvem). Zde v tomto příkladu exportujeme proměnnou A
(může to být také konstanta), třídu B
, a tak dále. Pomocí syntaxe export {}
můžete exportovat libovolný počet hodnot a kdokoli je může importovat pomocí syntaxe import { A, B }
. Nemusíte importovat všechny exportované hodnoty.
Exportovat hodnotu tam, kde byla deklarována, lze i jiným způsobem.
// values.ts
export const A = { name: "Apple" };
export class B {}
export function C(){}
export enum D{}
V uvedeném příkladu jsme exportovali všechny hodnoty tam, kde byly deklarovány. Hodnotu také nemusíte inicializovat, to můžete udělat později dole v souboru. To je mnohem příjemnější způsob ve srovnání s předchozím.
V obou případech jsou všechny vaše exportované členy přístupné v rámci jednoho souboru, pokud je potřebujete použít. To, že je u hodnoty uvedeno klíčové slovo export
, nemění její chování v rámci souboru modulu.
Výchozí export
Dosud jsme exportovali hodnoty, které lze importovat pouze uvedením názvu exportních členů, například import { A } from 'path/to/values'
, kde A
je zde exportní člen values.ts
. Tyto exporty se nazývají pojmenované.
Můžete také exportovat jedinou hodnotu, kterou lze importovat bez uvedení názvu. Tato hodnota by měla být identifikována klíčovým slovem default
spolu s klíčovým slovem export
.
// values.ts
var A = "Apple"; // can also use `let` or `const`
class A {}
function A(){}
enum A{}export default A;
Jako výchozí export je povoleno exportovat pouze jednu hodnotu, proto nemáte k dispozici výraz export default {...}
, se kterým byste mohli pracovat. Na rozdíl od pojmenovaných exportů nelze hodnotu exportovat jako výchozí s deklarací proměnné (nebo konstanty) s výjimkou deklarace function
a class
.
// 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
Můžete však exportovat hodnotu přímo jako výchozí export. To znamená, že jako výchozí exportní hodnotu lze exportovat libovolný výraz jazyka JavaScript.
// values.ts
export default "Hello"; // string
export default {}; // object
export default () => undefined; // function
export default function(){}; // function
export default class{}; // class
Protože výchozí export postrádá název exportu, můžeme pro něj při importu zadat libovolný název. V jednom souboru modulu můžete mít pojmenované exporty i výchozí export. Podobně můžete importovat hodnoty výchozího exportu a pojmenovaného exportu v jediné deklaraci import
, jak je uvedeno níže.
import Apple, { B, C } from 'path/to/values';
V uvedeném příkladu je Apple
jméno, které jsme použili pro výběr výchozího exportu z values.ts
. Výraz { B, C }
nebo název Apple
můžete z deklarace import
vypustit, pokud jej nebudete používat.
💡 Stejně jako u pojmenovaných exportů není při použití deklarace
import
povinné importovat výchozí hodnotu exportu. AleApple
musí být v signatuře deklaraceimport
před pojmenovanými exporty.
Můžete také použít syntaxi as default
v signatuře export {}
pro export hodnoty jako výchozí spolu s dalšími pojmenovanými exporty. Tomuto postupu se říká aliasing a je vysvětlen v další části.
// values.ts
var A = "Apple"; // can also use `let` or `const`
class B {}
function C(){}
enum D{}export { A as default, B, C, D };
💡 Důvod, proč nepoužívám a nedoporučuji export
default
, souvisí se zkušenostmi vývojářů. Jak jsme se naučili, v deklaraciimport
můžete uvést libovolné jméno pro výchozí exportní memeber modulu. Pokud je tato hodnota importována ve více souborech, můžete se na ni odkazovat s libovolným jménem podle vlastního výběru a mít různá jména pro stejný člen exportu téhož modulu je špatný DX.
Import a export Alias
V případě výchozího exportu můžete v deklaraci import
odkazovat na hodnotu s libovolným jménem podle vlastního výběru. To však neplatí v případě pojmenovaných exportů. Můžete však použít klíčové slovo as
pro odkaz na pojmenovanou hodnotu exportu s názvem podle vlastního výběru.
// program.ts
import A, { B as Ball } from 'path/to/values';console.log( B ); // ❌ Error: Cannot find name 'B'.
console.log( Ball ); // ✅ legal
V uvedeném příkladu importujeme B
z values.ts
, ale byl přejmenován na Ball
. Proto byste v souboru program.ts
místo B
použili Apple
. Když aliasujete exportní člen, původní název již v globálním rozsahu neexistuje, tudíž B
již nebude existovat v program.ts
.
💡 Nemůžete aliasovat výchozí exportní člen, protože již můžete zadat libovolný název podle svého výběru, ale můžete použít
import { default as defaultValue, }
.
Při exportu můžete hodnotu aliasovat také pomocí klíčového slova 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 };
V uvedeném příkladu jsme exportovali proměnnou A
jako výchozí export a funkci C
jako Cat
. Proto každý, kdo importuje tyto hodnoty ze souboru values.ts
, musí použít výchozí syntaxi importu pro A
a Cat
pro hodnotu C
.
Import všech pojmenovaných exportů
Můžete importovat všechny pojmenované exporty ze souboru pomocí syntaxe * as
.
// program.ts
import Apple, * as values from 'path/to/values';console.log( values.B );
console.log( values.C );
console.log( values.D );
Všechny pojmenované exporty z values.ts
budou uloženy pod values
, což bude objekt, kde keys
bude název členů exportu a values
budou exportované hodnoty členů exportu.
Znovuexporty
Importovanou hodnotu lze znovu exportovat běžným způsobem. Když něco importujete, máte odkaz na importovanou hodnotu a můžete použít stejnou hodnotu v syntaxi export
. Na tom není nic špatného.
// lib.ts
import A, { B, C as Cat, D } from 'path/to/values';export { D as default, A, B, Cat as C, D };
V uvedeném příkladu jste importovali několik hodnot z values.ts
uvnitř lib.ts
a exportovali je podle svých preferencí. To je skvělé, pokud chcete tyto importy použít v lib.ts
a také některé z nich exportovat, pokud je bude potřebovat jiný soubor při importu z lib.ts
.
Máme však také příkaz export ... from
pro export hodnot ze souboru, aniž byste je museli nejprve importovat. To je skvělé, když chcete mít jediný vstupní bod pro všechny importy v modulu.
// 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 };
V uvedeném příkladu exportujeme některé členy exportu values-1.ts
a values-2.ts
ze souboru lib.ts
(kromě výchozího exportu). Můžeme také použít aliasing v syntaxi export ... from
.
Syntaxe reexportu nemá vliv na aktuální soubor, proto můžete mít v souboru i své běžné exporty, jak je uvedeno výše. Na rozdíl od klíčového slova export
syntaxe export ... from
neumožňuje přístup k reexportovaným členům. Proto nebudete mít přístup k P
nebo Orange
v souboru lib.ts
.
// 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'.
Můžete exportovat všechny pojmenované exporty ze souboru pomocí syntaxe export * from
, protože máme možnost je při exportu přejmenovat pomocí export * as ... from
.
// lib.ts
export * from 'path/to/values-1';
export * as values2 from 'path/to/other/values-2';
Podle výše uvedeného příkladu, pokud values-1.ts
exportuje P
a Q
, pak může kdokoli importovat P
a Q
z lib.ts
. Všechny pojmenované exporty z values-2.ts
jsou však repexportovány jako pojmenovaný export values2
z lib.ts
, proto je třeba je importovat pomocí následující syntaxe:
// program.ts
import { P, Q, values2 } from 'path/to/lib';
K výchozímu exportu nelze přistupovat běžným způsobem pomocí syntaxe export ... from
. Výchozí export však můžete exportovat pomocí klíčového slova default
.
// lib.ts
export { default } from 'path/to/values-1';
export { default as Apple } from 'path/to/values-2';
V uvedeném příkladu je výchozí export lib.ts
výchozí exportní hodnotou values-2.ts
. Výchozí exportní hodnota values-2
bude exportována z lib.ts
jako Apple
pojmenovaný export.
Import pro vedlejší efekt
Dovedete si představit, že importujete soubor bez uvedení exportovaných členů?
import 'path/to/action';
V uvedeném příkladu importujeme action.ts
, ale uvedli jsme všechny členy exportované action.ts
. To je platná syntaxe, ale její použití vede ke zcela jinému výsledku.
Při importu souboru pomocí klíčového slova import
se nejprve provede a pak jsou pro import k dispozici všechny exportované členy. Proto by v níže uvedeném příkladu soubor importující PI
získal 3.14
jako číslo floating-point
.
export const PI = parseFloat( "3.14" ); // 3.14
Podle stejné logiky, pokud chcete importem souboru vytvořit vedlejší efekt, pak to zcela jistě můžete udělat. Pokud tedy například existuje soubor, který inicializuje některé globální proměnné, můžete jej importovat, aniž byste určili exportované členy tohoto souboru (také by v první řadě nemusel exportovat žádné).
Kompilátor jazyka TypeScript může vytvořit soubor svazku JavaScriptu (.js
) kompilací dvou nebo více souborů .ts
dohromady. Obvykle se to provádí tak, že se zadá vstupní TypeScript soubor a pak se projde jeho strom závislostí.
Strom závislostí se sestaví tak, že se prohlédnou všechny import
deklarace (závislosti) vstupního souboru a sledují se závislosti těchto importovaných souborů. Pokud chcete zahrnout soubor, jehož kód může za běhu vytvářet nějaké vedlejší účinky, pak byste měli tento soubor importovat bez uvedení exportovaných členů v syntaxi import
. To lze provést ve vstupním souboru nebo uvnitř libovolného souboru, který se nachází uvnitř stromu závislostí.
.
Napsat komentář