Не знаю где взять хорошую статью на эту тему (поделитесь в комментариях), напишу от себя.
Инверсия зависимостей (IoC) предполагает какое-то промежуточное ключ-значение (KV) хранилище через которое происходят ~все связи между модулями и библиотеками приложения. DI - конкретная реализация этого паттерна. Нужно это что бы контролировать эти связи, например:
- подменять реализацию в дев / прод сред или возвращать какой-то мок в тестах.
- разруливать DAG, циклические, асинхронные зависимости так что бы “оно просто работало”.
- упорядочивать (разделять) объявление и использование зависимостей для лучшего кодстайла и AOT анализа и оптимизаций.
> Иногда, сову натягивают на глобус и прикручивают к DI логер или начинают заниматься декорированием зависимостей, это точно плохая практика равносильная мидлварам, т.к. по коду зависимости или ее использования совсем не понятно откуда там взялась или поменялась какая-то логика.
Раньше у нас был cjs стандарт описания зависимостей - рекваеры можно было писать где угодно и с этим была куча проблем с точки зрения архитектурны, сборки, производительности. Радость была только в том что стандарт этот был мнимый и самопальный, поэтому мы могли спокойно его модифицировать - манкипатчить require и реализовывать какие-то фичи из первого пункта (привет, jest.mock). Стандарт импортов привнес более строгое и предсказуемое апи: топ левел == статические импорты и где-угодно асинхронные импорты. Для меня крайне интересно и не понятно почему у нас есть система импортов, но в ней не интегрированы фичи IoC - какие-то юзерспейс хуки. Хотя звоночки все равно доходят - import-maps.
Но перейдем к практическому вопросу, когда использовать доп либу для DI? Посмотрите на список выше и подумайте нужно ли вам что-то из этого?
- Мокать зависимости для тестов Jest уже умеет и для ESM, но он привносит доп сложность с точки зрения сетапа, да и работает не очень быстро. Если вы хотите запускать тесты быстро и с меньшими напрягами по сборке - используйте свой DI и какой-нибудь uvu.
- Если вы не хотите городить проверки на isLoading, а просто и прозрачно использовать лениво-подгружаемые зависимости, как данные с бекенда с React Suspense - берите DI-либу которая умеет в асинхроные резолвы.
- Если вы хотите лучше структурировать ваш код для лучшей читаемости или лучшего статического анализа - берите DI-либу которая не позволяет использовать Service locator, который считается антипаттерном (если не знакомы - гуглите сами, я не нашел хороших статей 😅).
Это было общее описание. А теперь про личный опыт и немного рекламы.
По моей практике сервис локатора на зависимости которые обрабатывают IO достаточно. Если у вас axios, можно обойтись лишь https://www.npmjs.com/package/axios-mock-adapter. Но иногда есть другие IO (postMessage), иногда нужно замокать какие-то таймеры или у сетевого слоя есть своя большая обвязка, или нужно замокать какие-то сильно изолированные модули, библиотеки. Я проектировал Reatom в том числе с расчетом на то что бы можно было легко тестировать порождаемые им сущности. Апишка впринципе задизайнена так что каждый атом / экшен работает только в переданном контексте (это нужно для множества фич), так почему бы этот контекст не использовать для моков - для этого уже есть пакет https://www.reatom.dev/packages/testing, он дает общий mock или mockAction для перехвата и подмены вызова какого-то сайд-эффекта. Круто то что в реатоме это просто работает, не нужен вообще никакой доп. код 🤗. Выглядит в использовании это максимально просто - мы либо оборачиваем каждый наш эффект в отдельный экшен, либо создаем сервис атом - тупо оборачиваем сервис в атом и обращаемся к сервису через чтение атома (кода это требует минимум). Пример есть в https://www.reatom.dev/core:
const api = ctx.get(apiAtom).P.S. в реатоме и логгер красивый есть.
UPD
- https://bespoyasov.ru/blog/di-ts-in-practice/