Export a import

Direktivy exportu a importu mají několik variant syntaxe.

V předchozím článku jsme viděli jednoduché použití, nyní se podívejme na další příklady.

Export před prohlášeními

Libovolnou deklaraci můžeme označit jako exportovanou umístěním export před ním, ať už je to proměnná, funkce nebo třída.

Například zde jsou všechny exporty platné:

// export an array
export let months = ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

// export a constant
export const MODULES_BECAME_STANDARD_YEAR = 2015;

// export a class
export class User {
 constructor(name) {
 this.name = name;
 }
}
Žádné středníky za exportní třídou/funkcí

Vezměte prosím na vědomí, že export před třídou nebo funkcí z ní nedělá výraz funkce. Je to stále deklarace funkce, i když exportovaná.

Většina příruček stylů JavaScriptu nedoporučuje středníky za deklaracemi funkcí a tříd.

Proto není potřeba středník na konci export class a export function :

export function sayHi(user) {
 alert(`Hello, ${user}!`);
} // no ; at the end

Exportovat kromě deklarací

Také můžeme vložit export samostatně.

Zde nejprve deklarujeme a poté exportujeme:

// 📁 say.js
function sayHi(user) {
 alert(`Hello, ${user}!`);
}

function sayBye(user) {
 alert(`Bye, ${user}!`);
}

export {sayHi, sayBye}; // a list of exported variables

…Nebo technicky můžeme zadat export výše uvedené funkce.

Importovat *

Obvykle vkládáme seznam toho, co se má importovat, do složených závorek import {...} , takto:

// 📁 main.js
import {sayHi, sayBye} from './say.js';

sayHi('John'); // Hello, John!
sayBye('John'); // Bye, John!

Ale pokud je toho k importu hodně, můžeme vše importovat jako objekt pomocí import * as <obj> , například:

// 📁 main.js
import * as say from './say.js';

say.sayHi('John');
say.sayBye('John');

Na první pohled se „importovat vše“ zdá jako skvělá věc, stručně řečeno, proč bychom měli někdy výslovně vyjmenovávat, co potřebujeme importovat?

No, existuje několik důvodů.

  1. Moderní nástroje pro vytváření (webpack a další) spojují moduly dohromady a optimalizují je, aby se urychlilo načítání a odstranily nepoužívané věci.

    Řekněme, že jsme přidali knihovnu třetí strany say.js do našeho projektu s mnoha funkcemi:

    // 📁 say.js
    export function sayHi() { ... }
    export function sayBye() { ... }
    export function becomeSilent() { ... }

    Nyní, pokud použijeme pouze jeden z say.js funkce v našem projektu:

    // 📁 main.js
    import {sayHi} from './say.js';

    …Pak to optimalizátor uvidí a odstraní ostatní funkce z přibaleného kódu, čímž se sestavení zmenší. Tomu se říká „třesení stromů“.

  2. Explicitní výpis toho, co se má importovat, poskytuje kratší názvy:sayHi() místo say.sayHi() .

  3. Explicitní seznam importů poskytuje lepší přehled o struktuře kódu:co se používá a kde. Usnadňuje podporu kódu a jeho refaktorování.

Importovat „jako“

Můžeme také použít as importovat pod různými názvy.

Například importujme sayHi do lokální proměnné hi pro stručnost a importujte sayBye jako bye :

// 📁 main.js
import {sayHi as hi, sayBye as bye} from './say.js';

hi('John'); // Hello, John!
bye('John'); // Bye, John!

Exportovat „jako“

Podobná syntaxe existuje pro export .

Exportujme funkce jako hi a bye :

// 📁 say.js
...
export {sayHi as hi, sayBye as bye};

Nyní hi a bye jsou oficiální názvy pro outsidery, které se mají používat při dovozu:

// 📁 main.js
import * as say from './say.js';

say.hi('John'); // Hello, John!
say.bye('John'); // Bye, John!

Exportovat výchozí

V praxi existují hlavně dva druhy modulů.

  1. Moduly, které obsahují knihovnu, sadu funkcí, jako je say.js výše.
  2. Moduly, které deklarují jednu entitu, např. modul user.js exportuje pouze class User .

Většinou je preferován druhý přístup, takže každá „věc“ sídlí ve svém vlastním modulu.

To samozřejmě vyžaduje spoustu souborů, protože všechno chce svůj vlastní modul, ale to vůbec není problém. Ve skutečnosti je navigace v kódu snazší, pokud jsou soubory dobře pojmenovány a strukturovány do složek.

Moduly poskytují speciální export default ("výchozí export") syntaxe, aby způsob "jedna věc na modul" vypadal lépe.

Zadejte export default před entitou k exportu:

// 📁 user.js
export default class User { // just add "default"
 constructor(name) {
 this.name = name;
 }
}

Může být pouze jeden export default na soubor.

…A pak jej importujte bez složených závorek:

// 📁 main.js
import User from './user.js'; // not {User}, just User

new User('John');

Dovozy bez složených závorek vypadají lépe. Častou chybou při zahájení používání modulů je zapomenutí složených rovnátek vůbec. Takže pamatujte, import potřebuje složené závorky pro pojmenované exporty a nepotřebuje je pro výchozí export.

Pojmenovaný export Výchozí export
export class User {...} export default class User {...}
import {User} from ... import User from ...

Technicky můžeme mít výchozí i pojmenované exporty v jednom modulu, ale v praxi je lidé obvykle nekombinují. Modul má buď pojmenované exporty, nebo výchozí.

Protože může existovat maximálně jeden výchozí export na soubor, exportovaná entita nemusí mít žádný název.

Toto jsou například všechny dokonale platné výchozí exporty:

export default class { // no class name
 constructor() { ... }
}
export default function(user) { // no function name
 alert(`Hello, ${user}!`);
}
// export a single value, without making a variable
export default ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

Neuvedení jména je v pořádku, protože existuje pouze jeden export default na soubor, takže import bez složených závorek ví, co importovat.

Bez default , takový export by způsobil chybu:

export class { // Error! (non-default export needs a name)
 constructor() {}
}

Výchozí název

V některých situacích default klíčové slovo se používá jako odkaz na výchozí export.

Chcete-li například exportovat funkci odděleně od její definice:

function sayHi(user) {
 alert(`Hello, ${user}!`);
}

// same as if we added "export default" before the function
export {sayHi as default};

Nebo v jiné situaci, řekněme modul user.js exportuje jednu hlavní „výchozí“ věc a několik pojmenovaných (výjimečně, ale stává se to):

// 📁 user.js
export default class User {
 constructor(name) {
 this.name = name;
 }
}

export function sayHi(user) {
 alert(`Hello, ${user}!`);
}

Zde je návod, jak importovat výchozí export spolu s pojmenovaným:

// 📁 main.js
import {default as User, sayHi} from './user.js';

new User('John');

A konečně, pokud importujete vše * jako objekt, pak default vlastnost je přesně výchozí export:

// 📁 main.js
import * as user from './user.js';

let User = user.default; // the default export
new User('John');

Slovo proti výchozím exportům

Pojmenované exporty jsou explicitní. Přesně pojmenovávají, co dovážejí, takže tyto informace od nich máme; to je dobrá věc.

Pojmenované exporty nás nutí používat pro import přesně ten správný název:

import {User} from './user.js';
// import {MyUser} won't work, the name must be {User}

…Při výchozím exportu vždy při importu zvolíme název:

import User from './user.js'; // works
import MyUser from './user.js'; // works too
// could be import Anything... and it'll still work

Takže členové týmu mohou používat různá jména k importu stejné věci, a to není dobré.

Obvykle, aby se tomu zabránilo a kód byl konzistentní, existuje pravidlo, že importované proměnné by měly odpovídat názvům souborů, např.:

import User from './user.js';
import LoginForm from './loginForm.js';
import func from '/path/to/func.js';
...

Některé týmy to přesto považují za vážnou nevýhodu výchozích exportů. Raději tedy vždy používají pojmenované exporty. I když je exportována pouze jedna věc, stále je exportována pod názvem, bez default .

To také trochu usnadňuje reexport (viz níže).

Znovu exportovat

Syntaxe „Reexport“ export ... from ... umožňuje importovat věci a okamžitě je exportovat (případně pod jiným názvem), takto:

export {sayHi} from './say.js'; // re-export sayHi

export {default as User} from './user.js'; // re-export default

Proč by to bylo potřeba? Podívejme se na praktický případ použití.

Představte si, že píšeme „balíček“:složku se spoustou modulů, s některými funkcemi exportovanými mimo (nástroje jako NPM nám umožňují takové balíčky publikovat a distribuovat, ale nemusíme je používat), a mnoho modulů jsou pouze „pomocníci“ pro interní použití v jiných modulech balíčků.

Struktura souboru může být tato:

auth/
 index.js
 user.js
 helpers.js
 tests/
 login.js
 providers/
 github.js
 facebook.js
 ...

Rádi bychom představili funkčnost balíčku prostřednictvím jediného vstupního bodu.

Jinými slovy, osoba, která by chtěla používat náš balíček, by měla importovat pouze z „hlavního souboru“ auth/index.js .

Takhle:

import {login, logout} from 'auth/index.js'

„Hlavní soubor“, auth/index.js exportuje všechny funkce, které bychom rádi poskytli v našem balíčku.

Myšlenka je taková, že lidé zvenčí, jiní programátoři, kteří používají náš balíček, by se neměli vměšovat do jeho vnitřní struktury a hledat soubory v naší složce balíčku. Exportujeme pouze to, co je nezbytné v auth/index.js a zbytek skryjte před zvědavýma očima.

Protože je skutečná exportovaná funkce rozptýlena v balíčku, můžeme ji importovat do auth/index.js a exportovat z něj:

// 📁 auth/index.js

// import login/logout and immediately export them
import {login, logout} from './helpers.js';
export {login, logout};

// import default as User and export it
import User from './user.js';
export {User};
...

Nyní mohou uživatelé našeho balíčku import {login} from "auth/index.js" .

Syntaxe export ... from ... je jen kratší zápis pro takový import-export:

// 📁 auth/index.js
// re-export login/logout
export {login, logout} from './helpers.js';

// re-export the default export as User
export {default as User} from './user.js';
...

Pozoruhodný rozdíl export ... from ve srovnání s import/export je, že reexportované moduly nejsou v aktuálním souboru dostupné. Takže uvnitř výše uvedeného příkladu auth/index.js nemůžeme použít reexportovaný login/logout funkce.

Opětovný export výchozího exportu

Výchozí export vyžaduje samostatné zpracování při opětovném exportu.

Řekněme, že máme user.js s export default class User a chtěli byste jej znovu exportovat:

// 📁 user.js
export default class User {
 // ...
}

Můžeme s tím narazit na dva problémy:

  1. export User from './user.js' nebude fungovat. To by vedlo k chybě syntaxe.

    Chcete-li znovu exportovat výchozí export, musíme napsat export {default as User} , jako ve výše uvedeném příkladu.

  2. export * from './user.js' reexportuje pouze pojmenované exporty, ale ignoruje výchozí.

    Pokud bychom chtěli znovu exportovat pojmenované i výchozí exporty, pak jsou zapotřebí dva příkazy:

    export * from './user.js'; // to re-export named exports
    export {default} from './user.js'; // to re-export the default export

Takové zvláštnosti opětovného exportu výchozího exportu jsou jedním z důvodů, proč někteří vývojáři nemají rádi výchozí exporty a preferují pojmenované.

Shrnutí

Zde jsou všechny typy export kterým jsme se věnovali v tomto a předchozích článcích.

Můžete si to ověřit tak, že si je přečtete a připomenete si, co znamenají:

  • Před deklarací třídy/funkce/…:
    • export [default] class/function/variable ...
  • Samostatný export:
    • export {x [as y], ...} .
  • Zpětný export:
    • export {x [as y], ...} from "module"
    • export * from "module" (neexportuje výchozí nastavení).
    • export {default [as y]} from "module" (výchozí nastavení opětovného exportu).

Import:

  • Import pojmenovaných exportů:
    • import {x [as y], ...} from "module"
  • Import výchozího exportu:
    • import x from "module"
    • import {default as x} from "module"
  • Importovat vše:
    • import * as obj from "module"
  • Importujte modul (jeho kód se spustí), ale žádný z jeho exportů nepřiřazujte proměnným:
    • import "module"

Můžeme vložit import/export příkazy v horní nebo dolní části skriptu, na tom nezáleží.

Takže technicky je tento kód v pořádku:

sayHi();

// ...

import {sayHi} from './say.js'; // import at the end of the file

V praxi jsou importy obvykle na začátku souboru, ale to jen pro větší pohodlí.

Upozorňujeme, že příkazy pro import/export nefungují, pokud jsou uvnitř {...} .

Podmíněný import, jako je tento, nebude fungovat:

if (something) {
 import {sayHi} from "./say.js"; // Error: import must be at top level
}

…Ale co když opravdu potřebujeme něco importovat podmíněně? Nebo ve správný čas? Například načíst modul na požádání, když je opravdu potřeba?

Dynamické importy uvidíme v příštím článku.