InversifyJS: IoC-контейнер для TypeScript-приложений

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

InversifyJS: Мощный IoC-контейнер для TypeScript

InversifyJS — это легковесный, но чрезвычайно мощный IoC (Inversion of Control) контейнер для TypeScript и JavaScript приложений. Он реализует паттерн Dependency Injection (DI), позволяя разработчикам создавать слабосвязанные, тестируемые и масштабируемые системы. Библиотека активно использует возможности TypeScript (декораторы, типы) для обеспечения строгой типизации и удобства разработки.

InversifyJS идеально подходит для серверных приложений на Node.js, а также для клиентских проектов с фреймворками вроде Angular, React или Vue. В этом обзоре мы разберём ключевые концепции и покажем, как быстро начать работу.

Основные концепции InversifyJS

Прежде чем писать код, важно понять три главные сущности библиотеки:

  • Контейнер (Container) — центральный объект, который управляет всеми зависимостями. Вы регистрируете в нём абстракции и их реализации.
  • Идентификаторы (Service Identifiers) — уникальные ключи, по которым контейнер находит нужную реализацию. Могут быть строками, символами или классами.
  • Декораторы — @injectable() и @inject() — указывают TypeScript, что класс может быть внедрён, и какие зависимости ему нужны.

Установка и настройка

Для начала установите InversifyJS и его типографические зависимости:

npm install inversify reflect-metadata --save
npm install --save-dev @types/node

Затем добавьте в tsconfig.json две обязательные опции:

{
  "compilerOptions": {
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "types": ["reflect-metadata"]
  }
}

После этого импортируйте reflect-metadata в главном файле приложения (обычно index.ts или main.ts):

import "reflect-metadata";

Первый пример: простой сервис

Создадим интерфейс и его реализацию. Обратите внимание на декоратор @injectable():

// interfaces/IWarrior.ts
export interface IWarrior {
    fight(): string;
}

// entities/Warrior.ts
import { injectable } from "inversify";

@injectable()
export class Warrior implements IWarrior {
    fight(): string {
        return "Warrior attacks with sword!";
    }
}

Теперь зарегистрируем эту связь в контейнере и получим экземпляр:

import { Container } from "inversify";
import { IWarrior } from "./interfaces/IWarrior";
import { Warrior } from "./entities/Warrior";

const container = new Container();

// Регистрируем абстракцию с её реализацией
container.bind<IWarrior>("IWarrior").to(Warrior);

// Получаем экземпляр
const warrior = container.get<IWarrior>("IWarrior");
console.log(warrior.fight()); // "Warrior attacks with sword!"

Внедрение зависимостей через конструктор

Настоящая сила DI раскрывается, когда один класс зависит от другого. Используем декоратор @inject():

// interfaces/IWeapon.ts
export interface IWeapon {
    use(): string;
}

// entities/Sword.ts
import { injectable } from "inversify";

@injectable()
export class Sword implements IWeapon {
    use(): string {
        return "Swing sword!";
    }
}

// entities/Knight.ts
import { injectable, inject } from "inversify";
import { IWarrior } from "../interfaces/IWarrior";
import { IWeapon } from "../interfaces/IWeapon";

@injectable()
export class Knight implements IWarrior {
    private _weapon: IWeapon;

    constructor(@inject("IWeapon") weapon: IWeapon) {
        this._weapon = weapon;
    }

    fight(): string {
        return this._weapon.use() + " The knight fights!";
    }
}

Регистрируем обе зависимости и получаем готовый объект с внедрённым оружием:

container.bind<IWeapon>("IWeapon").to(Sword);
container.bind<IWarrior>("IWarrior").to(Knight);

const knight = container.get<IWarrior>("IWarrior");
console.log(knight.fight()); // "Swing sword! The knight fights!"

Работа с модулями

Для больших проектов удобно группировать регистрации в модули. Модуль — это класс, реализующий интерфейс ContainerModule:

import { ContainerModule } from "inversify";

const warriorsModule = new ContainerModule((bind) => {
    bind                    

Рекомендаций