Что такое циклические зависимости между модулями и как их устранять?
Продвинутые темы
Циклическая зависимость возникает, когда модуль A импортирует B, а B прямо или через цепочку импортирует A. Такой цикл усложняет код и может приводить к ошибкам при загрузке.
Пример
// a.js
import { b } from './b.js';
export const a = () => b();
// b.js
import { a } from './a.js';
export const b = () => a();Чем это опасно
- В ES-модулях привязки живые, но значение может быть недоступно до выполнения модуля: обращение до инициализации даёт
ReferenceError(TDZ) илиundefined. - В CommonJS при цикле возвращается частично заполненный
exports. - Сложнее читать код и тестировать модули по отдельности.
- Ломается tree shaking и ухудшается разбиение на чанки.
- Возможны неожиданные побочные эффекты порядка загрузки.
Как находить
Инструменты madge, dpdm, плагин ESLint import/no-cycle, предупреждения сборщиков.
npx madge --circular srcКак устранять
- Выделить общий код в третий модуль, от которого зависят оба.
- Инвертировать зависимость: передавать функцию или интерфейс параметром (внедрение зависимости) вместо прямого импорта.
- Объединить тесно связанные модули в один.
- Отложить импорт до момента использования (динамический
import()) — как временная мера. - Соблюдать слои архитектуры: верхние слои знают о нижних, но не наоборот (FSD, Clean Architecture).
Профилактика
Единые правила импортов, публичный API модуля через index.ts, проверка циклов в CI.
Коротко для собеседования
Цикл — когда модули зависят друг от друга. Он приводит к недоинициализированным значениям и усложняет сопровождение. Лечится выделением общего модуля, инверсией зависимостей и правилами слоёв, а находят его через madge или import/no-cycle.