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 };

Такой стиль полезен, когда ты хочешь сначала объявить внутреннюю логику, а затем явно перечислить, что именно отдаешь наружу.

Реальные микро-сценарии

  1. Модуль валидации формы.
  • validation/email.js экспортирует isValidEmail.
  • signupForm.js импортирует и применяет перед отправкой.
  1. Модуль 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 помогают избежать конфликтов и улучшают контекст.
  • Корректные пути и явные контракты критичны для стабильной структуры проекта.
  • Хороший модуль экспортирует только нужное, а не все подряд.