ES6 Modules
JavaScript ES6 Modules — кольорові контейнери на порту як модульні блоки коду
До появи модулів весь JavaScript-код жив в одному глобальному просторі. Уяви: 10 файлів підключено через <script>, і кожен може перезаписати змінні іншого. Хаос! Модулі вирішують цю проблему -- кожен файл має власну область видимості, а ми явно вказуємо, що експортувати та імпортувати.
Навіщо потрібні модулі?
Без модулів:
// file1.js
var userName = "Олексій";
// file2.js
var userName = "Марія"; // перезаписує змінну з file1.js!
З модулями:
// file1.js
export const userName = "Олексій"; // доступна тільки при явному імпорті
// file2.js
export const userName = "Марія"; // жодних конфліктів
Переваги модулів:
- Ізоляція -- змінні не потрапляють у глобальну область
- Явні залежності -- видно, що від чого залежить
- Перевикористання -- легко використати код в іншому проекті
- Зручність -- великий проект розбивається на маленькі файли
Підключення модулів в HTML
Щоб браузер розумів import/export, потрібен атрибут type="module":
<script type="module" src="main.js"></script>
Модулі працюють тільки через HTTP-сервер (навіть локальний). Якщо відкрити HTML-файл напряму (file://), браузер заблокує імпорти через CORS. Використовуй npx serve або Live Server у VS Code.
У Node.js модулі теж працюють — потрібно додати "type": "module" в package.json:
Запуск ES-модулів у Node.js — помилка без type: module та виправлення
Модулі автоматично працюють у strict mode ("use strict"), тому помилки на кшталт використання незадекларованої змінної одразу спричинять помилку.
Named exports -- іменовані експорти
Файл може експортувати кілька значень по імені:
// math.js
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}
Або зібрати все в одному місці:
// math.js
const PI = 3.14159;
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
export { PI, add, multiply };
Named imports
Імпортуємо потрібне по імені у фігурних дужках:
// main.js
import { add, multiply } from './math.js';
console.log(add(2, 3)); // 5
console.log(multiply(4, 5)); // 20
Можна імпортувати з перейменуванням:
import { add as sum, PI as pi } from './math.js';
console.log(sum(2, 3)); // 5
console.log(pi); // 3.14159
Або імпортувати все як об'єкт:
import * as math from './math.js';
console.log(math.add(2, 3)); // 5
console.log(math.PI); // 3.14159
Default export -- експорт за замовчуванням
Кожен модуль може мати один default export. Це головне значення модуля:
// User.js
export default class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
greet() {
return `Привіт, я ${this.name}!`;
}
}
При імпорті default export не потрібні фігурні дужки, і можна дати будь-яке ім'я:
// main.js
import User from './User.js';
// або навіть:
import MyUser from './User.js'; // те саме!
const user = new User("Олексій", "alex@test.com");
console.log(user.greet());
Комбінування named і default
// api.js
const BASE_URL = "https://api.example.com";
async function fetchUsers() {
const res = await fetch(`${BASE_URL}/users`);
return res.json();
}
async function fetchPosts() {
const res = await fetch(`${BASE_URL}/posts`);
return res.json();
}
export { BASE_URL, fetchPosts };
export default fetchUsers;
// main.js
import fetchUsers, { BASE_URL, fetchPosts } from './api.js';
Загальноприйнята конвенція: один клас або одна головна функція на файл -- використовуй export default. Якщо файл містить набір утиліт чи констант -- використовуй named exports.
Re-export -- повторний експорт
Зручний спосіб зібрати всі експорти з кількох файлів в одному:
// utils/format.js
export function formatDate(date) { /* ... */ }
export function formatPrice(price) { /* ... */ }
// utils/validate.js
export function validateEmail(email) { /* ... */ }
export function validatePhone(phone) { /* ... */ }
// utils/index.js -- збираємо все
export { formatDate, formatPrice } from './format.js';
export { validateEmail, validatePhone } from './validate.js';
Тепер можна імпортувати з одного місця:
// main.js
import { formatDate, validateEmail } from './utils/index.js';
Організація коду по модулях
Типова структура проекту:
src/
constants.js -- константи (URL, налаштування)
utils.js -- допоміжні функції
api.js -- робота з API
dom.js -- маніпуляція DOM
main.js -- точка входу
Приклад: калькулятор по модулях
// operations.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
export const multiply = (a, b) => a * b;
export const divide = (a, b) => {
if (b === 0) throw new Error("Ділення на нуль!");
return a / b;
};
// history.js
const history = [];
export function addToHistory(entry) {
history.push(entry);
}
export function getHistory() {
return [...history]; // повертаємо копію
}
export function clearHistory() {
history.length = 0;
}
// calculator.js
import { add, subtract, multiply, divide } from './operations.js';
import { addToHistory, getHistory } from './history.js';
export function calculate(a, operator, b) {
let result;
switch (operator) {
case '+': result = add(a, b); break;
case '-': result = subtract(a, b); break;
case '*': result = multiply(a, b); break;
case '/': result = divide(a, b); break;
default: throw new Error(`Невідомий оператор: ${operator}`);
}
addToHistory(`${a} ${operator} ${b} = ${result}`);
return result;
}
export { getHistory };
// main.js
import { calculate, getHistory } from './calculator.js';
console.log(calculate(10, '+', 5)); // 15
console.log(calculate(10, '*', 3)); // 30
console.log(getHistory());
// ["10 + 5 = 15", "10 * 3 = 30"]
Зверни увагу: масив history в history.js -- приватний. Ніхто не може змінити його напряму, тільки через експортовані функції. Це принцип інкапсуляції -- модулі приховують деталі реалізації.
Named vs Default: коли що?
| Ситуація | Тип експорту | Приклад |
|---|---|---|
| Файл з однією сутністю (клас, компонент) | export default | export default class User |
| Файл з набором функцій / констант | named exports | export { add, subtract } |
| Бібліотека (bagato утиліт) | named exports | export { formatDate, parseDate } |
| Головна функція + допоміжні | default + named | export default fetch; export { BASE_URL } |
Підсумок
- Модулі ізолюють код -- кожен файл має свою область видимості
exportробить значення доступним для інших модулів- Named export:
export { a, b }-- імпорт у{ }:import { a, b } from - Default export:
export default X-- імпорт без{ }:import X from - Re-export:
export { func } from './module.js'-- для зручності import * as name-- імпортувати все як об'єкт- Розділяй код по файлах:
utils.js,api.js,constants.js
Що далі?
У наступному уроці вивчимо замикання (closures) -- одну з найважливіших концепцій JavaScript, яка пояснює, як функції "запам'ятовують" змінні.
Корисні посилання:
- javascript.info: Модулі -- вступ до модулів (українською)
- MDN: import -- повна документація import
- MDN: export -- повна документація export