Skip to content

Плохие практики

После каждого вызова register* используйте ссылку, которую вернул метод. Старая ссылка хранит прежний тип графа, хотя указывает на тот же runtime-контейнер.

Повторное использование старой ссылки

Каждый метод register* изменяет контейнер и возвращает тот же объект с расширенным generic-типом. TypeScript расширяет тип результата, но не меняет тип ссылок, созданных до вызова.

ts
class Consumer {
  constructor(readonly dependency: number) {}
}

const base = new Container()
const syncGraph = base
  .registerValue('dependency', 1)
  .registerClass('consumer', Consumer, ['dependency'])

// Код компилируется, потому что base по-прежнему имеет тип Container<{}>.
base.registerAsyncFactory('dependency', async () => 2, [])

// TypeScript видит number, но новая регистрация передаёт Promise<number>.
syncGraph.get('consumer').dependency

base и syncGraph указывают на один объект. Последний вызов перезаписывает runtime-регистрацию dependency, а тип syncGraph продолжает описывать исходный синхронный граф. Классы, зарегистрированные до перезаписи, сохраняют прежнюю классификацию зависимостей.

Почему компилятор разрешает такой код

Проверка дубликатов использует ключи из типа графа у текущей ссылки. Тип syncGraph содержит dependency, поэтому регистрация этого ключа через syncGraph вызовет ошибку типа. Тип base остаётся Container<{}>, где keyof T равен never.

TypeScript не обновляет generic-параметры у всех ссылок на мутабельный объект. В языке также нет linear- или affine-типов, которые могли бы пометить base использованным после регистрации. InferDI не добавляет поиск по registry в каждую регистрацию для отслеживания старых ссылок: такая проверка замедлила бы корректно собранные графы.

Используйте последнюю возвращённую ссылку

Собирайте граф одной цепочкой и работайте с её результатом:

ts
const container = new Container()
  .registerValue('dependency', 1)
  .registerClass('consumer', Consumer, ['dependency'])

container.get('consumer')

Не игнорируйте результат register* и не продолжайте регистрацию через старую ссылку. Модуль также должен возвращать контейнер после своей последней регистрации. Подробнее: Модули.

Не используйте register* для замены регистрации

Выбирайте каждую production-регистрацию один раз при сборке графа. Для выбора реализации по конфигурации используйте обычное ветвление или .use().

В тестах можно вызвать .override() до разрешения графа, если замена сохраняет исходные lifetime, lazy mode и async mode. .override() не может превратить синхронную регистрацию в декларативную асинхронную. Подробнее: Тестирование и подмена.