Search
Write a publication
Pull to refresh

Comments 6

Может я не прав и вы меня поправите, но в контексте реакта складывается ощущение, что некоторые недостатки никак не обыгрываются. Например, тот же props drilling. Вернее, обыгрываются но только для простых случаев.

Сферическая ситуация на основе кода в вашей статье.
Я компонент и я не хочу знать о существовании многих классов, имплементирующих некоторый интерфейс. Хочу знать только об одном интерфейсе логгера {log(): void}, ну или в крайнем случае о базовом классе, поскольку типов в рантайме нет. Если один из родителей хочет установить для своих детей в качестве логгера некоторый логгер, наследующий базовый класс, но отличающийся от зарегистрированного, ему по всей видимости нужно создать свой контейнер зависимостей, зарегать класс, и обернуть детей провайдером.
Это при условии, что либа построит своё дерево контейнеров, и будет разрешать зависимости по всему дереву от текущей ветви к корню.
В той же ситуации есть ещё один нюанс. Если некоторый класс, который поднимается внутри компоненты, захочет взять этот экземпляр логгера, то просто напросто не сможет либо из-за реактовского ограничения на использование хуков, либо из-за необходимости сослаться на контейнер текущей ветви, как на глобальную переменную.
Итого, придётся как в старые добрые времена прокидывать всё добро аргументами. Ну разве что сейчас можно прокидывать не зависимости, а ближайший контейнер, если библиотека приспособлена.

По статье, мне не хватило хоть какого-нибудь описания жиненных циклов. Одного упоминания маловато.

Буду рад ошибиться, потому как реакт местами дико неудобный, и DI действительно теоеретически может помочь в больших проектах

В описанном кейсе действительно DI не решит проблему до конца. Но давай посмотрим с другой стороны:

  • можно ли зарегистрировать два логгера глобально и использовать нужный в нужном контексте (например, по token);

  • почему возникла необходимость в отдельном подтипе логгера? Может быть, это можно решить внутри самого логгера — например, создать от него "копию" с другим поведением и передать её уже средствами React (через контекст/пропсы);

Когда мы говорим про использование DI в React-приложениях, важно понимать разницу в парадигмах. DI отлично подходит для бизнес-логики, инфраструктуры, управления состоянием вне UI — всего, что живёт "дольше", чем рендер компонента. React же предлагает свои механизмы для UI-слоя: props, context, state, hooks.

Я намеренно не реализовал иерархию контейнеров в @wroud/di. Хотя такая фича часто востребована (см. Angular, InversifyJS), она влечёт за собой огромную сложность и тонны edge-кейсов. Зато без неё библиотека остаётся предсказуемой и простой в использовании, что особенно важно для frontend-проектов.

Основная сложность, как мне кажется, не в том, что DI не работает, а в том, что React не даёт чёткой модели для архитектуры приложения. Он предоставляет мощный рендеринг-движок, но не говорит, как разделять бизнес-логику, работу с API, глобальное состояние и прочее. Поэтому и получается, что у каждого проекта свой подход.

Наш опыт в CloudBeaver подтверждает, что DI в больших frontend-проектах даёт ощутимую пользу. Мы используем InversifyJS (на тот момент @wroud/di ещё не было), и да, поначалу многим было непросто. Но со временем стало понятно: DI помогает структурировать код и заставляет отделять бизнес-логику от UI, что делает приложение гораздо легче для поддержки.

Что касается жизненного цикла: он очень простой. Контейнер обычно создаётся один раз на старте приложения и передаётся в React через Context. Есть три типа сервисов:

  • singleton — один экземпляр на всё приложение;

  • transient — новый экземпляр при каждом запросе;

  • scoped — для серверных приложений, где нужен отдельный scope на каждый запрос. В UI этот режим почти не используется.

Надеюсь, это немного прояснит общую картину. DI — не серебряная пуля, но в определённых слоях приложения он может сильно упростить архитектуру.

Я компонент и я не хочу знать о существовании многих классов, имплементирующих некоторый интерфейс. Хочу знать только об одном интерфейсе логгера {log(): void}, ну или в крайнем случае о базовом классе, поскольку типов в рантайме нет. Если один из родителей хочет установить для своих детей в качестве логгера некоторый логгер, наследующий базовый класс, но отличающийся от зарегистрированного, ему по всей видимости нужно создать свой контейнер зависимостей, зарегать класс, и обернуть детей провайдером.

Вы не поняли идею контейнера. Контейнер - это единственная точка входа для внедряемых зависимостей. Он по определению один на все приложение. Если у вас приложение использует в разных местах разные реализации логеров - это означает, что внедряемых логер является конфигурируемым. И тогда родитель устанавливает конфигурацию логера для своих потомков, но экземпляр логера они получают из DI, а вот конфигурацию из контекста.
По сути, тоже самое выше ответили.

Использовали react + mobx + react-ioc. Очень понравилось такая связка. По сути react-ioc на wroud можно заменить, вроде не сильно отличаются

вот это древность 😁
меня смущает только что react-ioc позиционирует себя как иерархическую систему, но по примерам и доке я быстро не смог понять в чем она заключается или что имеется в виду, поэтому мб и правда можно легко заменить

Пока что единственным инструментом который "продал" мне инъекцию зависимостей в жс стал effect. Почти все подобные инструменты пытались навязать мне запихивание любой сущности в класс и обмазывание декораторами, никогда не понимал почему кто-то считает что это удобно. Не говоря уже о том что никакой статической гарантии того что ты правильно построил зависимости не будет скорее всего, за этим сам следи. Эффект в сравнении со всем этим кажется таким простым и понятным.

Sign up to leave a comment.

Articles