Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Що таке живі прив'язки в ES-модулях і чим небезпечні циклічні імпорти?

Живі прив'язки (live bindings). Імпорт у ES-модулях - не копія значення, а посилання на змінну модуля-експортера. Якщо модуль змінить свою змінну, імпортери побачать нове значення:

// counter.js
export let count = 0;
export function increment() { count++; }

// main.js
import { count, increment } from './counter.js';
console.log(count);   // 0
increment();
console.log(count);   // 1 - значення оновилося
count = 5;            // TypeError: Assignment to constant variable

Змінити імпорт ззовні не можна - лише через функції самого модуля. У CommonJS навпаки: const { count } = require('./counter') - копія значення на момент виклику, і increment() її не змінить.

Циклічні імпорти - модуль A імпортує B, а B імпортує A (напряму чи через ланцюжок). ES-модулі це дозволяють, але порядок виконання стає неочевидним:

// a.js
import { b } from './b.js';
export const a = 'A';
console.log('a бачить', b);

// b.js
import { a } from './a.js';
export const b = 'B';
console.log('b бачить', a);   // ReferenceError: Cannot access 'a' before initialization

Запускаємо a.js. Він імпортує b.js - той виконується першим, повністю. На цей момент a.js ще не дійшов до рядка export const a, тож a - у тимчасовій мертвій зоні.

Коли цикл безпечний: якщо звернення до імпорту відбувається пізніше, не під час виконання модуля, а у функції, яку викличуть після завантаження всього графа:

// b.js
import { a } from './a.js';
export function describe() { return `b + ${a}`; }   // a читається при виклику - вже ініціалізовано

Завдяки живим прив'язкам функція побачить значення, щойно його буде встановлено.

Чим небезпечні цикли:

  • помилки залежать від того, який модуль імпортовано першим - змінили точку входу чи порядок імпортів, і код, що працював, падає;
  • з export default class чи const на верхньому рівні - ReferenceError при завантаженні; при наслідуванні class B extends A, де A ще не ініціалізовано, - теж;
  • HMR у Vite у циклічних графах часто перезавантажує всю сторінку замість одного модуля;
  • «бочки» (index.js, що реекспортує все) - типове джерело неявних циклів: компонент імпортує сусіда через index.js, а index.js імпортує сам компонент.

Як лікувати:

  • винести спільний код у третій модуль, від якого залежать обидва;
  • імпортувати напряму з файлу, а не через «бочку»;
  • передавати залежність параметром замість імпорту;
  • знаходити цикли інструментами: madge --circular, правило ESLint import/no-cycle.

Докладніше в документації: Модулі JavaScript

Схожі питання