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 Также в библиотеке
Charts для Swift: полное руководство с примерами
Coroutines для Kotlin: полное руководство с примерами
Мониторинг приложений Kotlin: библиотеки и инструменты для отслеживания производительности