Плохие практики
После каждого вызова register* используйте ссылку, которую вернул метод. Старая ссылка хранит прежний тип графа, хотя указывает на тот же runtime-контейнер.
Повторное использование старой ссылки
Каждый метод register* изменяет контейнер и возвращает тот же объект с расширенным generic-типом. TypeScript расширяет тип результата, но не меняет тип ссылок, созданных до вызова.
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').dependencybase и syncGraph указывают на один объект. Последний вызов перезаписывает runtime-регистрацию dependency, а тип syncGraph продолжает описывать исходный синхронный граф. Классы, зарегистрированные до перезаписи, сохраняют прежнюю классификацию зависимостей.
Почему компилятор разрешает такой код
Проверка дубликатов использует ключи из типа графа у текущей ссылки. Тип syncGraph содержит dependency, поэтому регистрация этого ключа через syncGraph вызовет ошибку типа. Тип base остаётся Container<{}>, где keyof T равен never.
TypeScript не обновляет generic-параметры у всех ссылок на мутабельный объект. В языке также нет linear- или affine-типов, которые могли бы пометить base использованным после регистрации. InferDI не добавляет поиск по registry в каждую регистрацию для отслеживания старых ссылок: такая проверка замедлила бы корректно собранные графы.
Используйте последнюю возвращённую ссылку
Собирайте граф одной цепочкой и работайте с её результатом:
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() не может превратить синхронную регистрацию в декларативную асинхронную. Подробнее: Тестирование и подмена.
