export и import
export и import
Технический фундамент export/import
Система ES-модулей работает со статически анализируемыми зависимостями:
- пути импорта известны до выполнения кода;
- инструменты сборки могут проверять и оптимизировать импорт заранее;
- именованные импорты создают "живые ссылки" на экспортируемые значения;
- импортировать можно только из верхнего уровня модуля (не внутри
if/forв обычном синтаксисе).
Поэтому точность имен и структуры экспортов критична для стабильной сборки и рефакторинга.
Смотри, что важно: "живые ссылки" означают, что импорт не копирует значение, а подключается к нему. Если модуль-источник обновляет экспортируемую переменную, новый результат увидят все, кто импортировал.
// counter.js
export let count = 0;
export function inc() {
count += 1;
}
// app.js
import { count, inc } from './counter.js';
console.log(count); // 0
inc();
console.log(count); // 1
При этом импортируемое имя - read-only на стороне импортера: нельзя делать count = 10 (это будет ошибкой). Если нужно "менять" состояние - меняй его через экспортируемые функции (inc) или через объект/класс с методами.
Смотри, что важно: обычный синтаксис import ... from ... действительно пишется на верхнем уровне. Если нужен условный импорт, существует динамический import(...) (он возвращает Promise), но это отдельный механизм и используется точечно.
if (needHeavyFeature) {
const module = await import('./heavyFeature.js');
module.run();
}
Что это и зачем это нужно
Когда модульность уже понятна, следующий шаг - научиться правильно связывать файлы между собой. Для этого в JavaScript есть export и import.
exportделает сущность доступной из файла.importподключает эту сущность в другом файле.
Ключевой момент: это формальный контракт между модулями. Ты явно указываешь, что открываешь наружу и что используешь извне.
Проверь себя: почему явные import-зависимости проще поддерживать, чем неявные глобальные переменные?
Базовый синтаксис export
Можно экспортировать функцию, константу, класс.
// price.js
export const TAX_RATE = 0.2;
export function applyTax(price) {
return price + price * TAX_RATE;
}
Здесь мы экспортируем две сущности: TAX_RATE и applyTax.
Смотри, что важно: экспорт это не вызов функции, а объявление публичного API модуля.
Базовый синтаксис import
Импорт именованных экспортов делается в фигурных скобках.
// app.js
import { TAX_RATE, applyTax } from './price.js';
console.log(TAX_RATE); // 0.2
console.log(applyTax(100)); // 120
Здесь часто путаются:
- путь должен быть корректным (
./для текущей папки); - имена в
import { ... }должны совпадать с экспортируемыми именами.
Проверь себя: что произойдет, если импортировать applyTaxes, которого нет в price.js?
Импорт с псевдонимом
Иногда имя занято или хочется сделать его более понятным в текущем контексте.
import { applyTax as addVat } from './price.js';
console.log(addVat(250));
Новый термин: alias (псевдоним) - локальное имя для импортируемой сущности.
Это удобно, когда в разных модулях есть однотипные имена (format, parse, create).
Экспорт после объявления
Можно экспортировать сущности внизу файла отдельным блоком.
const MIN_AGE = 18;
function canRegister(age) {
return age >= MIN_AGE;
}
export { MIN_AGE, canRegister };
Такой стиль полезен, когда ты хочешь сначала объявить внутреннюю логику, а затем явно перечислить, что именно отдаешь наружу.
Реальные микро-сценарии
- Модуль валидации формы.
validation/email.jsэкспортируетisValidEmail.signupForm.jsимпортирует и применяет перед отправкой.
- Модуль API-запросов.
api/userApi.jsэкспортируетgetUser,updateUser.profilePage.jsимпортирует только нужные функции.
Это снижает связность и делает код страниц чище.
Типичные ошибки новичков
- Путать именованный импорт и default-импорт (следующий урок).
- Указывать неверный путь к модулю.
- Экспортировать слишком много внутренних деталей, которые не должны быть публичными.
- Переименовывать импорт без необходимости, ухудшая читаемость.
// Ошибка: имя не совпадает
import { applyTaxes } from './price.js';
Дополнительный пример: namespace-импорт всего публичного API модуля.
import * as priceTools from './price.js';
console.log(priceTools.TAX_RATE);
console.log(priceTools.applyTax(300));
Этот подход удобен, когда нужно подчеркнуть источник функций (priceTools.*) и снизить риск конфликтов имен в большом файле.
Анти-провал: перед импортом проверь "публичное API" файла - какие сущности реально экспортируются и под какими именами.
Что будет, если изменить входные данные
В applyTax(100) получишь 120, а в applyTax(0) - 0. Если передать строку '100' без валидации, результат может быть неожиданным из-за приведения типов. Значит, модуль, который экспортирует бизнес-функцию, должен иметь понятный контракт по типам входа.
Проверь себя: где лучше валидировать тип - внутри applyTax или в вызывающем модуле? Почему?
Краткий итог
exportоткрывает сущности модуля наружу,importподключает их в другом файле.- Именованные импорты требуют точного совпадения имен.
- Псевдонимы через
asпомогают избежать конфликтов и улучшают контекст. - Корректные пути и явные контракты критичны для стабильной структуры проекта.
- Хороший модуль экспортирует только нужное, а не все подряд.