Контейнер DI управляет зависимостями. В контейнере регистрируются зависимости всех программных решений приложения: модулей, компонентов, сервисов, ресурсов — любых объектов. Через контейнер предоставляется доступ ко всем программным решениям приложения. Контейнер берёт на себя обязанность создавать и инициализировать решение по первому требованию. Контейнер запоминает подготовленное решение, чтобы не создавать и не инициализировать его повторно при очередном запросе решения. Контейнер реализует паттерн "Одиночка".
set(inject)
В метод set передаётся регистрация одного из типов InjectFactory
, InjectClass
, InjectValue
. Или массив регистраций.
set<Type, ExtType extends Type, Deps>(inject: InjectFactory<Type, ExtType, Deps>): this;
set<Type, ExtType extends Type, Deps>(inject: InjectClass<Type, ExtType, Deps>): this;
set<Type, ExtType extends Type>(inject: InjectValue<Type, ExtType>): this;
set(inject: InjectArray): this;
Можно передать заранее подготовленную регистрацию или описать её непосредственно в методе.
container.set({
token: FIRST,
depends: { logger: LOGS, config: FIRST_CFG },
constructor: First
})
Чтобы не вызывать метод set()
для каждой добавляемой регистрации, можно передать все регистрации в массиве. Но регистрации тогда должны быть заранее подготовлены (иначе не будут корректно выводиться типы зависимостей)!
container.set([configs, renderService, routerService, modalsService, httpClient, i18nService, logService])
Если регистрация не подготовлена, но её нужно передать в массив, то можно воспользоваться утилитой injectFactory()
, injectClass()
или injectValue()
для удобной типизации. То есть всё равно нужно подготовить регистрацию, но сделать это можно в момент добавления.
container.set([
configs,
renderService,
//...
injectClass({
token: FIRST,
depends: { logger: LOGS, config: FIRST_CFG },
constructor: First
})
])
Весь массив регистраций можно подготовить заранее. Например, в неком модуле приложения можно определить массив со всеми регистрациями на сервисы, компоненты, АПИ и ресурсы модуля.
// Регистарция модуля со всеми его составными частями (тоже регистрация)
export const catalogFeature = [
articlesApi,
categoriesApi,
injectTranslations,
articlesStore,
categoriesStore,
];
В итоге в контейнер будет добавляться заранее подготовленная коллекция (массив) регистраций. В метод set
можно передавать массив массивов регистраций любой вложенности.
// Подключение модуля catalogFeature с массивом регистраций в приложение
container.set([configs, renderService, catalogFeature])
get(token)
async get<Type>(token: TokenInterface<Type>): Promise<Type>
Любое решение можно выбрать из контейнера по токену методом get(token)
. Если выбор решения осуществляется впервые, то оно будет создано, инициализировано и возвращено. Если осуществляется повторная выборка решения, то возвращается ранее созданное решение (объект). Если по токену не будет найдена регистрация, то возникнет ошибка.
const i18n = await container.get(I18N);
const logs = await container.get(LOG_SERVICE);
Метод get(token)
асинхронный - возвращает Promise
. Возможна ситуация, когда повторная выборка решения осуществляется при ещё не выполненной первой выборке с тем же токеном. Но контейнер не будет повторно создавать и инициализировать решение, а вернет обещание (promise) от первой выборки. Все получат один и тот же экземпляр решения.
getMapped(depends)
async getMapped<Deps extends Record<string, TokenInterface>>(depends: Deps): Promise<TypesFromTokens<Deps>>
Выбор множества решений по указанной карте токенов. Решения будут возвращены под теми же ключами, под которыми указаны токены в аргументе depends
. Формат аргумента depends
такой же, как в регистрациях зависимостей.
const { i18n, logs } = await container.getMapped({ i18n: I18N, logs: LOG_SERVICE })
Метод асинхронный и будет ждать готовности всех запрошенных решений.
getWithSuspense(token) и getMappedWithSuspense(depends)
getWithSuspense<Type>(token: TokenInterface<Type>): Type
Синхронный выбор программного решения по токену. Но в случаи не готовности запрашиваемого решения будет выкидываться исключение с обещанием (Promise
) . Promise не возвращается методом! Если решение уже готово, то будет успешно возвращено.
try {
const i18n = container.get(I18N);
console.log('Решение уже было подготовлено!', i18n)
} catch (e) {
if (e instanceof Promise) {
console.log('Решение ещё не готово!'))
e.then((i18n) => console.log('Решение подготовилось!', i18n))
} else {
throw e;
}
}
Метод предназначен для реализации React хуков с обработкой ожидания через компонент <Suspense>
, и напрямую редко используется. Аналогичным образом работает метод getMappedWithSuspense(depends)
- только выборка уже по карте токенов.
Хуки используются в React компонентах для выборки программных решения из DI контейнера. DI контейнер прокидывается в React-контекст сервисом рендера, нл доступ в компонентах предоставляется не к DI контейнеру, а к программным решениям из контейнера. В хуках указываются токены на необходимые решения.
useSolution(token)
useSolution<Type>(token: Token<Type>): Type
Хук useSolution(token)
используется для выборки программного решения из DI контейнера по токену. В React-приложении должен применяться компонент <Suspense>
, так как хук может выбросить исключение в случае ожидания готовности выбираемого решения. Это означает, что пока решение не будет готово, выполнение React компонента будет сброшено, и будет отображаться fallback-компонент от <Suspense>
(например, спиннер). Как только обещание готовности запрашиваего решения выполнится, компонент будет заново отрендерен, и хук успешно вернёт запрошенное по токену решение.
function MyCompoentn () {
const modals = useSolution(MODALS);
const open = () => modals.open(CONFIRM_MODAL)
return (
<div onClick={openConfirm}>Отправить</div>
)
}
useSolutionMap(depends)
useSolutionMap<Deps extends Record<string, Token>>
Хук useSolutionMap(depends)
используется для выборки множества программных решений из DI контейнера по указанной карте токенов. Решения будут возвращены с теми же ключами, что и токены в аргументе depends
. Формат аргумента depends
аналогичен формату регистраций зависимостей. Логика хука схожа с логикой хука useSolution(token)
, но обещание готовности будет одно для всех запрашиваемых решений. В случае с раздельным вызовом useSolution
компонент будет перерендериваться несколько раз — для каждого решения, которое ещё не готово. С хуком useSolutionMap(depends)
выброс обещания в исключение произойдёт один раз — сразу для всех запрашиваемых решений.
const { i18n, store } = useSolutionMap({i18n: I18N_TOKEN, store: STORE_TOKEN})
useSolutionPending(token)
useSolutionPending<Type>(token: Token<Type>): {
instance: Type | undefined;
isSuccess: boolean;
isWaiting: boolean;
isError: boolean;
}
Хук useSolutionPending(token)
используется для выборки программного решения из DI контейнера по токену с возвратом признаков ожидания, успеха или ошибки. Для использования хука не требуется компонент <Suspense>
, так как хук не выбрасывает исключений.
Если запрашиваемое решение ещё не готово, выполнение компонента не прекратится. Но в этом случае необходимо проверять статусы, которые вернёт хук — isSuccess
, isWaiting
, isError
.
Если решение ещё не готово (так как оно асинхронно создаётся), то оно не будет возвращено, но будет возвращён признак isWaiting = true
. Когда решение будет готово, компонент перерендерится, и при следующем вызове хук useSolutionPending
вернёт признак isSuccess = true
и экземпляр самого решения в instance
.
Если при подготовке решения возникнет фатальная ошибка, хук вернёт признак isError = true
.
const i18n = useSolutionPending(I18N_TOKEN);
console.log(i18n.instance);
console.log(i18n.isSuccess);
сonsole.log(i18n.isWaiting);
console.log(i18n.isError);
if (i18n.instance) {
// Действия если сервис i18n получен
}
CONTAINER
Иногда нужна зависимость на сам контейнер, для этого существует токен на контейнер. Фактически контейнер сам себя регистрирует в контейнере.
const container = useSolution(CONTAINER);
deleteValue(token)
async deleteValue<Type>(token: TokenInterface<Type>): Promise<void>
Удаление экземпляра программного решения в DI контейнере. После чего при выборке этого же решения из DI контейнера оно снова будет создаваться и инициироваться.
events
Контейнер отправляет события о создании экземпляра решения onCreate
и его удалении onDelete
(когда вызывается deleteValue()
). На эти события можно подписаться.
container.events.on('onCreate', <Type extends object>({ token, value }: { token: Token<Type>; value: Type }) => {
// обработка события
}
container.events.on('onDelete', ({ token }: { token: Token }) => {
// обработка события
}
Например, сервис dump
отслеживает событие onCreate
, чтобы всем инициализируемым программным решениям сразу передавать их сохраненное состояние (состояние сохраняется после рендере на сервере).
Для работы с событиями используется класс Events
из React-Solution. Для отписки от события применяется метод events.off()
. Подробнее см. в описании класса Events
.
← Регистрация | .. →