Egy átfogó útmutató a TypeScript “modulrendszeréről” (példákkal)
On október 16, 2021 by adminModulszabvány
Az EcmaScript szabványosította, hogyan kell modulokat importálnunk a JavaScriptben, és ez a szabvány két kulcsszó, a import és a export körül forog. Mivel a modulrendszer a JavaScriptben egyre népszerűbb, minden évben újabb és újabb változtatások érkeznek ehhez a szabványhoz.
Ezzel a bemutatóval a TypeScript modulrendszerének szemantikáját fogjuk megvizsgálni (amely leginkább az EcmaScript szabványra hasonlít), és azt, hogyan működik a import és a export kulcsszó.
Nevezett export
A export kulcsszó egy fájlban meghatározott értéket (változót) elérhetővé tesz más modulfájlokban történő importálásra. Amikor egy modult egy másik modulfájlba importálunk a import kulcsszó használatával, az importáló fájl megadhatja azokat az értékeket, amelyekhez az importált modulból hozzá akar férni.
// program.ts
import { A } from 'path/to/values';console.log( A ); // "Apple"
A fenti példában a program.ts fájlban a values.ts értéket importáljuk, és a A exportált tagot kivonjuk. Ez azt jelenti, hogy a values.ts-nek valamilyen formában exportálnia kell a A értéket. A A exponálásának több módja is van.
// values.ts
var A = "Apple"; // can also use `let` or `const`
class B {}
function C(){}
enum D{}export { A, B, C, D };
Exportálhat bármilyen elérhető TypeScript értéket érvényes azonosítóval (névvel). Itt ebben a példában egy A változót (lehet egy konstans is), egy B osztályt és így tovább exportálunk. A export {} szintaxis használatával annyi értéket exportálhat, amennyit csak akar, és bárki importálhatja őket a import { A, B } szintaxis használatával. Nem kell az összes exportált értéket importálni.
Az értékek exportálásának van egy másik módja is, ahol deklarálták őket.
// values.ts
export const A = { name: "Apple" };
export class B {}
export function C(){}
export enum D{}
A fenti példában az összes értéket ott exportáltuk, ahol deklaráltuk őket. Az értéket sem kell inicializálni, ezt a fájlban később is megtehetjük. Ez egy sokkal szebb megoldás az előzőhöz képest.
Mindkét esetben az összes exportált tagunk elérhető ugyanabban a fájlban, ha szükségünk van rájuk. Az, hogy export kulcsszó van egy értéken, nem változtatja meg annak viselkedését a modulfájlon belül.
Default Export
Eleddig olyan értékeket exportáltunk, amelyeket csak az export tagok nevének megadásával lehet importálni, mint például import { A } from 'path/to/values', ahol A itt a values.ts export tagja. Ezeket nevezzük nevesített exportnak.
Egyetlen értéket is exportálhatunk, amely név megadása nélkül importálható. Ezt az értéket a default kulcsszóval kell azonosítani a export kulcsszóval együtt.
// values.ts
var A = "Apple"; // can also use `let` or `const`
class A {}
function A(){}
enum A{}export default A;
Egyetlen értéket lehet alapértelmezett exportként exportálni, ezért nincs export default {...} kifejezés, amivel dolgozhat. A név szerinti exportálással ellentétben egy értéket nem lehet alapértelmezettként exportálni egy változó (vagy konstans) deklarációval, kivéve a function és class deklarációt.
// 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
Viszont egy értéket közvetlenül exportálhatunk alapértelmezett exportként. Ez azt jelenti, hogy bármilyen JavaScript-kifejezést exportálhatunk alapértelmezett exportértékként.
// values.ts
export default "Hello"; // string
export default {}; // object
export default () => undefined; // function
export default function(){}; // function
export default class{}; // class
Mivel az alapértelmezett exportnak nincs exportnév, importáláskor bármilyen nevet megadhatunk neki. Ugyanabban a modulfájlban lehetnek megnevezett exportok és egy alapértelmezett export is. Hasonlóképpen importálhatjuk az alapértelmezett export és a megnevezett export értékeit egyetlen import deklarációban, ahogy az alább látható.
import Apple, { B, C } from 'path/to/values';
A fenti példában a Apple az a név, amellyel az alapértelmezett exportot a values.ts-ből gyűjtöttük össze. A { B, C } kifejezést vagy a Apple nevet elhagyhatja a import deklarációból, ha nem fogja használni.
💡 A megnevezett exportokhoz hasonlóan a
importdeklaráció használata esetén sem kötelező az alapértelmezett exportérték importálása. De aApple-nek aimportdeklaráció aláírásában a megnevezett exportok előtt kell állnia.
A as default szintaxist is használhatja a export {} aláírásában, hogy egy értéket alapértelmezettként exportáljon más megnevezett exportokkal együtt. Ezt aliasingnek hívják, és a következő szakaszban magyarázzuk el.
// values.ts
var A = "Apple"; // can also use `let` or `const`
class B {}
function C(){}
enum D{}export { A as default, B, C, D };
💡 Az ok, amiért nem használom és nem ajánlom a
defaultexportot, a fejlesztői tapasztalatokkal függ össze. Mint tanultuk, aimportdeklarációban bármilyen nevet megadhatunk egy modul alapértelmezett export tagjának. Ha ezt az értéket több fájlba importáljuk, akkor tetszőleges névvel hivatkozhatunk rá, és az, hogy ugyanazon modul ugyanazon exporttagjának különböző nevei vannak, rossz DX.
Import And Export Alias
Az alapértelmezett export esetében a import deklarációban tetszőleges névvel hivatkozhatunk az értékre. Ez azonban nem így van a nevesített exportok esetében. A as kulcsszóval azonban tetszőleges névvel hivatkozhat a megnevezett exportértékre.
// program.ts
import A, { B as Ball } from 'path/to/values';console.log( B ); // ❌ Error: Cannot find name 'B'.
console.log( Ball ); // ✅ legal
A fenti példában a B értéket importáljuk a values.ts-ből, de azt átneveztük Ball-re. Ezért a program.ts fájlban a B helyett a Apple-et használnánk. Amikor egy exporttagot aliasol, az eredeti név már nem létezik a globális hatókörben, ezért a B már nem fog létezni a program.ts fájlban.
💡 Az alapértelmezett exporttagot nem aliasolhatja, mivel már bármilyen tetszőleges nevet megadhat, de a
import { default as defaultValue, }-t használhatja.
Az exportálás során az as kulcsszó használatával alias értéket is megadhatunk.
// 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 };
A fenti példában a A változót alapértelmezett exportként, a C függvényt pedig Catként exportáltuk. Ezért bárkinek, aki ezeket az értékeket a values.ts fájlból importálja, a A értékhez az alapértelmezett import szintaxist, a C értékhez pedig a szintaxist kell használnia.
Minden megnevezett export importálása
Egy fájlból az összes megnevezett exportot a * as szintaxis használatával importálhatja.
// program.ts
import Apple, * as values from 'path/to/values';console.log( values.B );
console.log( values.C );
console.log( values.D );
Itt az összes névre szóló export a values.ts-ből a values alá lesz mentve, amely egy objektum lesz, ahol a keys az exportált tagok neve, a values pedig az exportált tagok exportált értékei lesznek.
Újraexportálás
Egy importált értéket a szokásos módon lehet újraexportálni. Ha valamit importálsz, akkor van egy hivatkozásod az importált értékre, és ugyanezt az értéket használhatod a export szintaxisban. Ezzel nincs semmi baj.
// lib.ts
import A, { B, C as Cat, D } from 'path/to/values';export { D as default, A, B, Cat as C, D };
A fenti példában a values.ts-ből importáltál néhány értéket a lib.ts-en belül, és tetszés szerint exportáltál. Ez nagyszerű, ha ezeket az importált értékeket a lib.ts-ben szeretné használni, valamint néhányat exportálni, ha egy másik fájlnak szüksége van rájuk, amikor a lib.ts-ből importál.
Megvan azonban a export ... from utasítás is, amellyel exportálhatunk értékeket egy fájlból anélkül, hogy először importálnunk kellene őket. Ez akkor nagyszerű, ha egyetlen belépési pontot szeretnénk tartani a modulunk összes importált értékéhez.
// 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 };
A fenti példában a values-1.ts és a values-2.ts exportált tagjainak egy részét exportáljuk a lib.ts fájlból (kivéve az alapértelmezett exportot). A export ... from szintaxisban is használhatunk aliasinget.
A re-export szintaxis nem érinti az aktuális fájlt, ezért a fenti példában látható módon a fájlban a szokásos exportjaink is lehetnek. A export kulcsszóval ellentétben a export ... from szintaxis nem enged hozzáférést az újraexportált tagokhoz. Ezért nem fog tudni hozzáférni a P vagy Orange tagokhoz a lib.ts fájlban.
// 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'.
A export * from szintaxissal exportálhatja az összes megnevezett exportált tagot egy fájlból, mivel a export * as ... from szintaxis segítségével átnevezheti őket exportálás közben.
// lib.ts
export * from 'path/to/values-1';
export * as values2 from 'path/to/other/values-2';
A fenti példából kiindulva, ha a values-1.ts exportálja a P és Q neveket, akkor bárki importálhatja a P és Q neveket a lib.ts címről. Azonban a values-2.ts összes nevesített exportja a lib.ts nevesített values2 exportjaként kerül újraexportálásra, ezért ezeket a következő szintaxissal kell importálni.
// program.ts
import { P, Q, values2 } from 'path/to/lib';
Az alapértelmezett exportot a export ... from szintaxissal normál módon nem lehet elérni. De exportálhatja az alapértelmezett exportot a default kulcsszó használatával.
// lib.ts
export { default } from 'path/to/values-1';
export { default as Apple } from 'path/to/values-2';
A fenti példában a lib.ts alapértelmezett export értéke a values-2.ts alapértelmezett export értéke. Az values-2 alapértelmezett export értéke a lib.ts-ből a Apple nevű exportként lesz exportálva.
Import mellékhatásért
El tudod képzelni, hogy egy fájlt úgy importálsz, hogy nem adod meg az exportált tagokat?
import 'path/to/action';
A fenti példában a action.ts importáljuk, de megadtunk minden olyan tagot, amelyet a action.ts exportál. Ez egy érvényes szintaxis, de nagyon eltérő eredményt kapunk vele.
Az import kulcsszóval történő importáláskor először végrehajtódik, majd az összes exportált tag importálhatóvá válik. Ezért az alábbi példában egy PI importáló fájl a 3.14-t kapná floating-point számként.
export const PI = parseFloat( "3.14" ); // 3.14
Az ugyanezt a logikát követve, ha egy fájl importálásával akarsz egy mellékhatást létrehozni, akkor azt teljesen megteheted. Így például, ha van egy fájl, amely inicializál néhány globális változót, akkor azt importálhatjuk anélkül, hogy megadnánk a fájl exportált tagjait (az is lehet, hogy eleve nem exportál semmit).
A TypeScript fordító képes létrehozni egy JavaScript bundle fájlt (.js) két vagy több .ts fájl összefordításával. Ez általában úgy történik, hogy megad egy belépő TypeScript fájlt, majd végigmegy annak függőségi fáján.
A függőségi fa úgy épül fel, hogy megnézzük a belépő fájl összes import deklarációját (függőségét), és követjük ezeknek az importált fájloknak a függőségeit. Ha be akarunk vonni egy olyan fájlt, amelynek kódja futásidőben mellékhatásokat okozhat, akkor azt a fájlt az exportált tagok megadása nélkül kell importálni a import szintaxisban. Ez megtehető a belépési fájlban vagy bármely olyan fájlon belül, amely a függőségi fán belül van.
Vélemény, hozzászólás?