Perintah export dan import memiliki beberapa varian sintaks.
Pada artikel sebelumnya kita melihat cara penggunaan yang sederhana, sekarang mari telusuri lebih banyak contoh.
Export sebelum deklarasi
Kita dapat memberi label deklarasi apapus untuk diekspor dengan menempatkan export
sebelumnya, baik itu variabel, fungsi atau kelas.
Misalnya, dibawah ini semua export valid:
// export sebuah array
export let months = ['Jan', 'Feb', 'Mar','Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
// export sebuah konstanta
export const MODULES_BECAME_STANDARD_YEAR = 2015;
// export a class
export class User {
constructor(name) {
this.name = name;
}
}
Mohon diperhatikan bahwa export
sebelum sebuah kelas atau fungsi tidak menjadikannya sebagai ekspresi fungsi. Ini masih sebuah deklarasi fungsi meskipun sudah diekspor.
Kebanyakan panduan gaya penulisan Javascript tidak merekomendasikan titik koma setelah deklarasi fungsi dan kelas.
Itu mengapa tidak perlu menambahkan titik koma di akhir export class
dan export function
:
export function sayHi(user) {
alert(`Hello, ${user}!`);
} // tidak ada ; diakhir
Export selain dari deklarasi
Kita juga dapat meletakkan perintah export
secara terpisah.
Di bawah ini kita mendeklarasikan terlebih dahulu, lalu kemudian melakukan ekspor:
// 📁 say.js
function sayHi(user) {
alert(`Hello, ${user}!`);
}
function sayBye(user) {
alert(`Bye, ${user}!`);
}
export {sayHi, sayBye}; // daftar variabel yang diekspor
…Atau, secara teknis kita dapat meletakkan export
diatas fungsi juga.
Import *
Biasanya, kita membuat daftar apa yang akan kita impor di dalam kurung kurawal import {...}
, seperti ini:
// 📁 main.js
import {sayHi, sayBye} from './say.js';
sayHi('John'); // Halo, John!
sayBye('John'); // Selamat tinggal, John!
Tetapi jika ada banyak yang harus diimpor, kita dapat melakukan impor semuanya sebagai sebuah objek menggunakan import * as <obj>
. Sebagai contoh:
// 📁 main.js
import * as say from './say.js';
say.sayHi('John');
say.sayBye('John');
Kesan pertama terkait “impor semuanya” adalah terdengar seperti sesuatu yang keren dan singkat ketika ditulis. Mengapa kita harus secara eksplisit membuat daftar apa yang perlu kita impor?
Jadi, ini adalah beberapa alasannya.
-
Perkakas penggabung yang modern (webpack dan lainnya) menggabungkan semua modul sekaligus dan mengoptimalkannya untuk mempercepat proses pemuatan dan menghapus modul yang tidak digunakan.
Katakanlah kita menambahkan sebuah pustaka pihak ketiga
say.js
ke dalam proyek dengan banyak fungsi:// 📁 say.js export function sayHi() { ... } export function sayBye() { ... } export function becomeSilent() { ... }
Sekarang jika kita hanya menggunakan salah satu dari fungsi
say.js
di proyek kita:// 📁 main.js import { sayHi } from './say.js';
…Maka kemudian ketika proses pengoptimalan berjalan akan melihatnya dan menghapus fungsi lainnya (yang tidak digunakan) dari kode yang digabungkan, ini membuat kode hasil penggabungan lebih kecil. Itulah yang disebut “tree-shaking”
-
Mendaftarkan secara eksplisit apa yang akan diimpor dengan nama yang lebih pendek:
sayHi()
sebagai ganti darisay.sayHi()
. -
Daftar import eksplisit memberikan gambaran yang lebih baik tentang struktur kode: apa yang digunakan dan dimana. Itu membuat dukungan kode dan proses refactoring lebih mudah.
Import “as”
Kita juga dapat menggunakan as
untuk mengimpor dengan nama yang berbeda.
Sebagai contoh, cobalah impor sayHi
ke dalam variabel lokal hi
agar lebih singkat, dan impor sayBye
sebagai bye
:
// 📁 main.js
import {sayHi as hi, sayBye as bye} from './say.js';
hi('John'); // Halo, John!
bye('John'); // Selamat Tinggal, John!
Export “as”
Sintaks serupa berlaku juga untuk export
Mari ekspor fungsi sebagai hi
dan bye
:
// 📁 say.js
...
export {sayHi as hi, sayBye as bye};
Sekarang hi
dan bye
adalah nama resmi yang diekspor dan kemudian dapat digunakan dalam impor:
// 📁 main.js
import * as say from './say.js';
say.hi('John'); // Halo, John!
say.bye('John'); // Selamat Tinggal, John!
Export default
Dalam praktiknya, terdapat dua jenis modul.
- Modul yang berisi pustaka, paket fungsi, seperti
say.js
diatas. - Modul yang mendeklarasikan sebuah entitas, misalnya modul
user.js
hanya mengeksporclass User
.
Kebanyakan pendekatan kedua yang lebih disukai, jadi untuk setiap benda
berada dalam modulnya sendiri.
Tentu itu membutuhkan banyak berkas, karena semuanya menginginkan modulnya sendiri. Tetapi itu tidak menjadi masalah sama sekali. Sebenarnya, navigasi kode menjadi lebih mudah jika berkas diberi nama dengan baik dan terstruktur didalam direktori.
Modul menyediakan perintah khusus export default
(“ekspor bawaan”) untuk membuat cara “satu hal per modul” terlihat lebih baik.
Tambahkan export default
sebelum entitas yang akan diekspor:
// 📁 user.js
export default class User { // tambahkan saja "default"
constructor(name) {
this.name = name;
}
}
Mungkin hanya ada satu export default
dalam setiap berkas:
…Dan kemudian impor tanpa menggunakan kurung kurawal:
// 📁 main.js
import User from './user.js'; // Bukan {User}, Hanya User
new User('John');
Impor tanpa kurung kurawal terlihat bagus. Kesalahan umum ketika memulai menggunakan modul adalah ketika sepenuhnya melupakan kurung kurawal. Jadi, ingat import
memerlukan kurung kurawal untuk ekspor bernama dan tidak memerlukannya untuk ekspor bawaan.
Ekspor bernama | Ekspor bawaan |
---|---|
export class User {...} |
export default class User {...} |
import {User} from ... |
import User from ... |
Secara teknis, kita dapat memiliki keduanya (ekspor bawaan dan ekspor bernama) dalam satu modul yang sama, tetapi pada praktiknya, orang tidak mencampurnya. Sebuah modul memiliki antara ekspor bernama atau ekspor bawaan.
Karena mungkin hanya ada paling banyak satu ekspor bawaan tiap berkas, entitas yang diekspor mungkin tidak memiliki nama.
Misalnya, dibawah ini adalah ekspor bawaan yang benar-benar valid:
export default class { // tidak ada nama kelas
constructor() { ... }
}
export default function (user) {
// tidak ada nama fungsi
alert(`Hello, ${user}!`);
}
// ekspor nilai tunggal tanpa membuat variabel
export default ['Jan', 'Feb', 'Mar', 'Apr', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
Tidak memberikan nama tidak masalah, karena hanya ada satu export default
setiap berkas, jadi import
tanpa tanda kurung kurawal tahu apa yang perlu diimpor.
Tanpa default
, ekspor seperti itu akan memberikan sebuh error:
export class { // Error! (selain 'default export' memerlukan sebuah nama)
constructor() {}
}
Penamaan “default”
Pada situasi tertentu kata kunci default
digunakan untuk mereferensikan ekspor bawaan.
Misalnya, untuk mengekspor fungsi yang terpisah dari tempat deklarasinya:
function sayHi(user) {
alert(`Hello, ${user}!`);
}
// sama seperti jika menambahkan "export default" sebelum fungsi
export { sayHi as default };
Atau pada situasi yang lainnya, katakanlah sebuah modul user.js
mengekspor satu hal utama “bawaan”, dan beberapa yang dinamai (jarang terjadi, tetapi bisa saja terjadi):
// 📁 user.js
export default class User {
constructor(name) {
this.name = name;
}
}
export function sayHi(user) {
alert(`Hello, ${user}!`);
}
Berikut cara mengimpor ekspor bawaan bersama dengan yang ekspor bernama:
// 📁 main.js
import {default as User, sayHi} from './user.js';
new User('John');
Dan terakhir, jika mengimpor semua *
sebagai sebuah objek, maka properti default
sama persis dengan ekspor bawaan:
// 📁 main.js
import * as user from './user.js';
let User = user.default; // ekspor bawaan
new User('John');
Sebuah kata yang menentang ekspor bawaan
Ekspor bernama eksplisit. Mereka secara persis menyebutkan apa yang mereka impor, jadi kita bisa mendapatkan informasi itu dari mereka; itu adalah sesuatu yang bagus.
Ekspor bernama memaksa kita untuk menggunakan nama yang tepat untuk melakukan impor:
import { User } from './user.js';
// impor {MyUser} tidak akan berfungsi, penamaannya haruss {User}
…Sedangkan untuk ekspor bawaan, kita selalu dapat memilih nama ketika mengimpor:
import User from './user.js'; // berfungsi
import MyUser from './user.js'; // berfungsi juga
// Dapat melakukan impor apa saja... dan itu tetap berfungsi
Kemungkinan anggota tim menggunakan penamaan yang berbeda untuk mengimpor hal yang sama, dan itu tidak baik.
Biasanya, untuk menghindari hal tersebut dan menjaga konsistensi kode terdapat aturan bahwa variabel yang di impor harus sesuai dengan nama berkas, misalnya:
import User from './user.js';
import LoginForm from './loginForm.js';
import func from '/path/to/func.js';
...
Namun, beberapa tim menganggapnya sebagai kelemahan serius dari ekspor default. Jadi, mereka lebih selalu suka menggunakan ekspor bernama. Meskipun hanya satu hal yang diekspor, itu masih diekspor dengan nama, tanpa default
.
Itu juga membuat ekspor ulang (lihat di bawah) sedikit lebih mudah.
Ekspor Ulang
“Ekspor ulang” sintaks export ... from ...
memperbolehkann untuk mengimpor sesuatu dan segera mengekspornya kembali (memungkinkan dengan nama yang berbeda) seperti ini:
export { sayHi } from './say.js'; // ekspor ulang sayHi
export { default as User } from './user.js'; // ekspor ulang bawaan
Kenapa hal tersebut diperlukan? mari lihat praktik penggunaannya.
Bayangkan kita menulis sebuah “paket”: sebuah direktori dengan banyak modul, dengan beberapa fungsi yang diekpor ke luar (perkakas seperti NPM memungkinkan kita untuk menerbitkan dan mendistribusikan paket seperti itu), dan kebanyakan modul hanyalah “pembantu” untuk penggunaan internal di paket modul lainnya.
Struktur berkas bisa seperti ini:
auth/
index.js
user.js
helpers.js
tests/
login.js
providers/
github.js
facebook.js
...
Kita ingin menunjukkan fungsionalitas paket melalui satu titik masuk, “Berkas utama” auth/index.js
dapat digunakan seperti ini.
import { login, logout } from 'auth/index.js';
Idenya adalah bahwa orang luar (pengembang) yang menggunakan paket kita tidak boleh ikut campur dengan struktur internalnya, serta mencari berkas didalam direktori paket kita. Kita hanya mengekspor apa yang penting di dalam auth/index.js
dan menyembunyikan sisaya dari pengintaian.
Karena fungsionalitas yang diekspor sebenarnya tersebar diantara paket, kita dapat mengimpornya ke dalam auth/index.js
dan kemudian kembali mengekspornya:
// 📁 auth/index.js
// impor login/logout dan kemudian segera mengekspornya kembali
import {login, logout} from './helpers.js';
export {login, logout};
// impor default sebagai User kemudian mengekspornya
import User from './user.js';
export {User};
...
Sekarang pengguna dari paket kita dapat import { login }
dari "auth/index.js"
.
Sintaks export ... from ...
hanyalah notasi pendek untuk proses impor-ekspor.
// 📁 auth/index.js
// impor login/logout dan kemudian segera mengekspornya kembali
export {login, logout} from './helpers.js';
// impor default sebagai User kemudian mengekspornya
export {default as User} from './user.js';
...
Ekspor ulang ekspor bawaan
Ekspor bawaan memerlukan penanganan terpisah ketika melakukan ekspor ulang.
Misalnya kita memiliki user.js
dan kita ingin melakukan ekspor ulang kelas User
tersebut:
// 📁 user.js
export default class User {
// ...
}
-
export User form './user.js'
tidak dapat digunakan. Apa yang salah?… Ini adalah kesalahan sintaks!Untuk melakukan ekspor ulang ekspor bawaan, kita harus menuliskan
export { default as User }
seperti contoh diatas. -
export * from './user.js'
mengekspor ulang hanya ekspor bernama, tetapi mengabaikan ekspor bawaan.Jika kita ingin melakukan ekspor ulang keduanya (ekspor bernama dan ekspor bawaan), maka diperlukan dua pernyataan seperti berikut:
export * from './user.js'; // ekspor ulang ekspor bernama export { default } from './user.js'; // ekspor ulang ekspor bawaan
Keanehan dari proses mengekspor ulang ekspor bawaan adalah salah satu alasan mengapa beberapa pengembang tidak menyukainya.
Ringkasan
Berikut merupakan semua jenis export
yang kita bahas di artikel ini dan sebelumnya.
Kamu dapat mengeceknya secara mandiri dengan membacanya dan mengingat apa maksudnya:
Ekspor:
- Sebelum deklarasi dari sebuah kelas/fungsi/…:
export [default] class/function/variable ...
- Ekspor mandiri:
export { x [as y], ... } from "module"
- Ekspor ulang:
export {x [as y], ...} from "module"
export * from "module"
(tidak mengekspor ulang bawaan).export {default [as y]} from "module"
(ekspor ulang bawaan).
Impor:
- Ekspor bernama dari modul:
import {x [as y], ...} from "module"
- Ekspor bawaan:
import x from "module"
import {default as x} from "module"
- Semuanya:
import * as obj from "module"
- Impor modul (ini menjalankan kode), tetapi tidak disimpan kedalam variabel:
import "module"
Kita dapat meletakkan pernyataan import/export
di bagian atas atau bawah dari skrip, itu tidak masalah.
Jadi secara teknis kode ini tidak dipermasalahkan:
sayHi();
// ...
import { sayHi } from './say.js'; // impor dibagian bawah berkas
Dalam praktiknya, impor biasanya dilakukan pada awal sebuah berkas, tetapi itu hanya untuk memberikan kenyamanan lebih.
Harap diperhatikan bahwa pernyataan import/export tidak dapat digunakan jika didalam {...}
.
Sebuah impor bersyarat seperti ini tidak akan dapat berfungsi:
if (something) {
import { sayHi } from './say.js'; // Error: import must be at top level (impor harus diluar pernyataan if dann { ... } sehingga kode tersebut tidak akan bekerja)
}
…Tetapi bagaimana jika kita benar-benar perlu menhimpor sesuatu dengan syarat tertentu? atau pada waktu yang tepat? Seperti memuat modul berdasarkan permintaan, pada saat itu benar-benar dibutuhkan?
Oleh karena itu, kita akan mempelajari impor dinamis di artikel selanjutnya.