Wyczerpujący przewodnik po „Module System” w TypeScript (z przykładami)
On 16 października, 2021 by adminStandard modułów
EcmaScript ustandaryzował jak powinniśmy importować moduły w JavaScript i standard ten obraca się wokół dwóch słów kluczowych, import
i export
. Ponieważ system modułów w JavaScript staje się coraz bardziej popularny, co roku pojawiają się nowe zmiany w tym standardzie.
W tym poradniku przyjrzymy się semantyce systemu modułów w TypeScript (który w większości przypomina standard EcmaScript) oraz temu, jak działają słowa kluczowe import
i export
.
Eksporty nazwane
Słowo kluczowe export
sprawia, że wartość (zmienna) zdefiniowana w pliku jest dostępna do importu w innych plikach modułów. Kiedy moduł jest importowany w innym pliku modułu przy użyciu słowa kluczowego import
, plik importujący może określić wartości, do których chce mieć dostęp z importowanego modułu.
// program.ts
import { A } from 'path/to/values';console.log( A ); // "Apple"
W powyższym przykładzie importujemy values.ts
w pliku program.ts
i wyodrębniamy A
wyeksportowanego członka. Oznacza to, że values.ts
musi wyeksportować wartość A
w jakiejś formie. Istnieje wiele sposobów ekspozycji A
.
// values.ts
var A = "Apple"; // can also use `let` or `const`
class B {}
function C(){}
enum D{}export { A, B, C, D };
Możesz wyeksportować dowolną dostępną wartość TypeScript z poprawnym identyfikatorem (nazwą). W tym przykładzie eksportujemy zmienną A
(może to być również stała), klasę B
, i tak dalej. Używając składni export {}
, możesz wyeksportować tyle wartości, ile chcesz i każdy może je zaimportować używając składni import { A, B }
. Nie musisz importować wszystkich wyeksportowanych wartości.
Istnieje inny sposób na wyeksportowanie wartości tam, gdzie została zadeklarowana.
// values.ts
export const A = { name: "Apple" };
export class B {}
export function C(){}
export enum D{}
W powyższym przykładzie wyeksportowaliśmy wszystkie wartości tam, gdzie zostały zadeklarowane. Nie musisz również inicjalizować wartości, możesz to zrobić później w dół pliku. Jest to znacznie przyjemniejszy sposób w porównaniu do poprzedniego.
W obu przypadkach wszyscy twoi członkowie eksportu są dostępni w tym samym pliku, jeśli potrzebujesz ich użyć. Posiadanie słowa kluczowego export
na wartości nie zmienia jej zachowania wewnątrz pliku modułu.
Default Export
Do tej pory eksportowaliśmy wartości, które mogą być importowane tylko przez podanie nazwy członków eksportu, takich jak import { A } from 'path/to/values'
, gdzie A
jest członkiem eksportu values.ts
. Są to tak zwane eksporty nazwane.
Dozwolone jest również wyeksportowanie pojedynczej wartości, która może być importowana bez podawania nazwy. Ta wartość powinna być określona słowem kluczowym default
wraz ze słowem kluczowym export
.
// values.ts
var A = "Apple"; // can also use `let` or `const`
class A {}
function A(){}
enum A{}export default A;
Jedynie pojedyncza wartość jest dozwolona do wyeksportowania jako domyślny eksport, stąd nie masz wyrażenia export default {...}
do pracy. W przeciwieństwie do nazwanych eksportów, wartość nie może być wyeksportowana jako domyślna z deklaracją zmiennej (lub stałej) z wyjątkiem deklaracji function
i 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
Jednakże możesz wyeksportować wartość bezpośrednio jako domyślny eksport. Oznacza to, że każde wyrażenie JavaScript może zostać wyeksportowane jako domyślna wartość eksportu.
// values.ts
export default "Hello"; // string
export default {}; // object
export default () => undefined; // function
export default function(){}; // function
export default class{}; // class
Ponieważ domyślny eksport nie posiada nazwy eksportu, możemy nadać mu dowolną nazwę podczas importu. Możesz mieć nazwane eksporty i domyślny eksport w tym samym pliku modułu. Podobnie, możesz zaimportować wartości domyślnego eksportu i nazwanego eksportu w pojedynczej deklaracji import
, jak pokazano poniżej.
import Apple, { B, C } from 'path/to/values';
W powyższym przykładzie, Apple
jest nazwą, której użyliśmy do pobrania domyślnego eksportu z values.ts
. Możesz opuścić wyrażenie { B, C }
lub nazwę Apple
z deklaracji import
, jeśli nie zamierzasz jej używać.
💡 Podobnie jak w przypadku nazwanych eksportów, nie jest obowiązkowe importowanie domyślnej wartości eksportu, gdy używana jest deklaracja
import
. Ale,Apple
musi znajdować się przed nazwanymi eksportami w podpisie deklaracjiimport
.
Możesz również użyć składni as default
w podpisie export {}
, aby wyeksportować wartość jako domyślną wraz z innymi nazwanymi eksportami. Nazywa się to aliasing i jest wyjaśnione w następnej sekcji.
// values.ts
var A = "Apple"; // can also use `let` or `const`
class B {}
function C(){}
enum D{}export { A as default, B, C, D };
💡 Powód, dla którego nie używam i nie polecam
default
eksportu ma związek z doświadczeniem deweloperskim. Jak się dowiedzieliśmy, możesz podać dowolną nazwę dla domyślnego memebera eksportu modułu w deklaracjiimport
. Jeśli ta wartość jest importowana w wielu plikach, możesz odwołać się do niej z dowolną wybraną przez siebie nazwą, a posiadanie różnych nazw dla tego samego członka eksportu tego samego modułu jest złym DX.
Import And Export Alias
W przypadku domyślnego eksportu, możesz odwołać się do wartości z dowolną wybraną przez siebie nazwą w deklaracji import
. Jednak nie jest tak w przypadku eksportów nazwanych. Można jednak użyć słowa kluczowego as
, aby odwołać się do nazwanej wartości eksportu z wybraną przez siebie nazwą.
// program.ts
import A, { B as Ball } from 'path/to/values';console.log( B ); // ❌ Error: Cannot find name 'B'.
console.log( Ball ); // ✅ legal
W powyższym przykładzie importujemy B
z values.ts
, ale zmieniono jego nazwę na Ball
. W związku z tym w pliku program.ts
użyjemy Apple
zamiast B
. Kiedy aliasujesz członka eksportu, oryginalna nazwa nie istnieje już w zakresie globalnym, stąd B
nie będzie już istnieć w pliku program.ts
.
💡 Nie możesz aliasować domyślnego członka eksportu, ponieważ możesz już podać dowolną nazwę, ale możesz użyć
import { default as defaultValue, }
.
Dozwolone jest również aliasowanie wartości podczas eksportowania przy użyciu as
słowa kluczowego.
// 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 };
W powyższym przykładzie wyeksportowaliśmy zmienną A
jako domyślny eksport i funkcję C
jako Cat
. Dlatego każdy, kto importuje te wartości z pliku values.ts
, musi użyć domyślnej składni importu dla A
i Cat
dla wartości C
.
Importuj wszystkie nazwane eksporty
Możesz zaimportować wszystkie nazwane eksporty z pliku za pomocą składni * as
.
// program.ts
import Apple, * as values from 'path/to/values';console.log( values.B );
console.log( values.C );
console.log( values.D );
Tutaj, wszystkie nazwane eksporty z values.ts
zostaną zapisane pod values
, który będzie obiektem, gdzie keys
będzie nazwą członków eksportu, a values
będzie wyeksportowanymi wartościami członków eksportu.
Re-eksporty
Zaimportowana wartość może zostać ponownie wyeksportowana w normalny sposób. Kiedy coś importujesz, masz odniesienie do wartości importu i możesz użyć tej samej wartości w składni export
. Nic nie jest w tym złego.
// lib.ts
import A, { B, C as Cat, D } from 'path/to/values';export { D as default, A, B, Cat as C, D };
W powyższym przykładzie zaimportowałeś kilka wartości z values.ts
wewnątrz lib.ts
i wyeksportowałeś zgodnie z własnymi preferencjami. Jest to świetne, jeśli chcesz użyć tego importu w lib.ts
, jak również wyeksportować niektóre z nich, jeśli inny plik potrzebuje ich, gdy importuje z lib.ts
.
Jednakże mamy również export ... from
oświadczenie, aby wyeksportować wartości z pliku bez konieczności importowania ich najpierw. Jest to świetne rozwiązanie, gdy chcesz zachować jeden punkt wejścia dla wszystkich importów w twoim module.
// 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 };
W powyższym przykładzie eksportujemy niektórych członków eksportu values-1.ts
i values-2.ts
z pliku lib.ts
(z wyjątkiem domyślnego eksportu). Możemy również użyć aliasingu w składni export ... from
.
Składnia re-eksportu nie wpływa na bieżący plik, stąd możesz mieć swoje zwykłe eksporty w pliku, jak pokazano powyżej. W przeciwieństwie do słowa kluczowego export
, składnia export ... from
nie pozwala na dostęp do ponownie wyeksportowanych członków. Stąd nie będziesz mógł uzyskać dostępu do P
lub Orange
w pliku 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'.
Możesz wyeksportować wszystkie nazwane eksporty z pliku przy użyciu składni export * from
, tak jak masz możliwość zmiany ich nazw podczas eksportowania przy użyciu export * as ... from
.
// lib.ts
export * from 'path/to/values-1';
export * as values2 from 'path/to/other/values-2';
Z powyższego przykładu, jeśli values-1.ts
eksportuje P
i Q
, to każdy może zaimportować P
i Q
z lib.ts
. Jednak wszystkie nazwane eksporty values-2.ts
są reeksportowane jako values2
nazwany eksport lib.ts
, dlatego musisz zaimportować je za pomocą następującej składni.
// program.ts
import { P, Q, values2 } from 'path/to/lib';
Nie można uzyskać dostępu do domyślnego eksportu za pomocą składni export ... from
w normalny sposób. Ale można wyeksportować domyślny eksport przy użyciu słowa kluczowego default
.
// lib.ts
export { default } from 'path/to/values-1';
export { default as Apple } from 'path/to/values-2';
W powyższym przykładzie domyślny eksport lib.ts
jest domyślną wartością eksportu values-2.ts
. Domyślna wartość eksportu values-2
zostanie wyeksportowana z lib.ts
jako Apple
o nazwie export.
Import dla efektu ubocznego
Czy można sobie wyobrazić importowanie pliku bez określania wyeksportowanych członków?
import 'path/to/action';
W powyższym przykładzie importujemy action.ts
, ale określiliśmy dowolnych członków eksportowanych przez action.ts
. Jest to poprawna składnia, ale jest używana do uzyskania zupełnie innego wyniku.
Gdy importujesz plik używając słowa kluczowego import
, jest ono najpierw wykonywane, a następnie wszyscy członkowie eksportu są dostępni do importu. Stąd w poniższym przykładzie plik importujący PI
otrzymałby 3.14
jako floating-point
number.
export const PI = parseFloat( "3.14" ); // 3.14
Podążając za tą samą logiką, jeśli chcesz utworzyć efekt uboczny przez importowanie pliku, to możesz to całkowicie zrobić. Tak więc na przykład, jeśli istnieje plik, który inicjalizuje niektóre zmienne globalne, możesz go zaimportować bez określania wyeksportowanych członków tego pliku (mógłby on również nie eksportować żadnego z nich).
Kompilator TypeScript może utworzyć plik JavaScript bundle (.js
) przez kompilację dwóch lub więcej plików .ts
razem. Zwykle odbywa się to poprzez dostarczenie wejściowego pliku TypeScript, a następnie przejście przez jego drzewo zależności.
Drzewo zależności jest konstruowane przez spojrzenie na wszystkie import
deklaracje (zależności) pliku wejściowego i śledzenie zależności tych importowanych plików. Jeśli chcesz dołączyć plik, którego kod może tworzyć pewne efekty uboczne w czasie wykonywania, to powinieneś zaimportować ten plik bez określania eksportowanych członków w składni import
. Można to zrobić w pliku wejściowym lub wewnątrz dowolnego pliku, który znajduje się wewnątrz drzewa zależności.
Dodaj komentarz