Skip to content

Гайды / Работа с DI

В этом разделе описано, как интегрировать Nuxoblivius с контейнерами зависимостей (DI). Для примера мы будем использовать популярный контейнер Inversify.

1. Подготовка

Перед началом работы с Nuxoblivius и интеграцией с DI необходимо выполнить базовую настройку проекта.

Установите зависимости

bash
npm install nuxoblivius inversify

Инициализация контейнера

ts
import { Container } from "inversify";

const container = new Container();

2. Создание UserService

Создадим сервис, который будет хранить и управлять данными пользователя. Он будет зарегистрирован как singleton, чтобы одно состояние переиспользовалось во всём приложении.

ts
import { subStore } from "nuxoblivius";

interface IUser {
  id: number;
  name: string;
}

export class UserService {
  private user?: IUser;

  public setUser(user: IUser): void {
    this.user = user;
  }

  public getUser(): IUser {
    if (!this.user) {
      throw new Error("User is empty");
    }

    return this.user;
  }

  public isLoggedIn(): boolean {
    return this.user !== null;
  }
}

export const useUserService = () => subStore(UserService);
ts
import { defineFactory } from "nuxoblivius";

interface IUser {
  id: number;
  name: string;
}

export class UserService {
  private user?: IUser;

  public setUser(user: IUser): void {
    this.user = user;
  }

  public getUser(): IUser {
    if (!this.user) {
      throw new Error("User is empty");
    }

    return this.user;
  }

  public isLoggedIn(): boolean {
    return this.user !== null;
  }
}

export const useUserService = defineFactory(UserService);

3. Регистрация UserService

Добавляем UserService в DI-контейнер:

ts
import { Container } from "inversify";
import { UserService, useUserService } from "@/service/UserService";

export const container = new Container();

container
  .bind(UserService)
  .toDynamicValue(() => useUserService())
  .inSingletonScope();

4. Внедрение зависимостей

После регистрации сервисов и стора в контейнере, их можно внедрять (инъектировать) в другие классы.

ts
import { subStore, Record } from "nuxoblivius";
import type { UserService } from "@/service/UserService";

export class PostService {
  private posts = Record.new("/api/posts", []);

  // Добавляем зависимость как аргумент
  constructor(private userService: UserService) {}

  async getUserPosts() {
    if (!this.userService.isLoggedIn()) {
      throw new Error("User is not loggined");
    }

    const user = this.userService.getUser();

    return await this.posts.query({ userId: user.id }).get();
  }
}

export const usePostService = (userSerivce: UserService) = subStore(PostService, userService);
ts
import { defineFactory, Record } from "nuxoblivius";
import type { UserService } from "@/service/UserService";

export class PostService {
  private posts = Record.new("/api/posts", []);

  // Добавляем зависимость как аргумент
  constructor(private userService: UserService) {}

  async getUserPosts() {
    if (!this.userService.isLoggedIn()) {
      throw new Error("User is not loggined");
    }

    const user = this.userService.getUser();

    return await this.posts.query({ userId: user.id }).get();
  }
}

export const usePostService = defineFactory(PostService);

Так-же регистрация в контейнере:

ts
import { Container } from "inversify";
import { UserService, useUserService } from "@/service/UserService";
import { PostService, usePostService } from "@/service/PostService";

export const container = new Container();

container
  .bind(UserService)
  .toDynamicValue(() => useUserService())
  .inSingletonScope();

container.
  .bind(PostService)
  .toDynamicValue(() => usePostService(
    // Подключаем зависимость
    container.get<UserService>(UserService)
  ))
  .inSingletonScope();

5. Внедренние в Vue Файл

Внедрение работает через контейнер, так-же как и в примерах выше.

vue
<script setup lang="ts">
import { container } from "@/config/di";
import { PostService } from "@/service/PostService";

const postService = container.get<PostService>(PostService);

const userPosts = await postService.getUserPosts();
</script>
<template>
  {{ userPosts }}
</template>

Итого

Во фронтенде DI используют реже, чем в бэкенде, но он реально даёт плюсы, особенно если проект крупный и модульный. Вот основные плюсы:

  1. Ослабленная связность (Loose Coupling)

    Компоненты и сервисы не знают, кто именно создаёт их зависимости. Они просто описывают «мне нужен UserService», а контейнер уже решает, какую реализацию дать. Это снижает «спагетти-зависимости» в коде.

  2. Лёгкое тестирование

    С DI можно в контейнер подложить мок или фейковую реализацию.Например, вместо настоящего ApiService в тестах используется FakeApiService, и код компонента менять не нужно.

  3. Гибкая замена реализаций

    Если сервис меняется (например, LocalStorageAuthService -> JwtAuthService), то компоненты этого не замечают: достаточно переназначить биндинг в контейнере.

  4. Управление жизненным циклом

    DI позволяет контролировать:

    • singleton (один на всё приложение),
    • transient (новый объект при каждом запросе),
    • scoped (на сессию, на страницу и т.д.). Это полезно для сторов, кэшей, сервисов пользователя.
  5. Единый подход к управлению состоянием

    DI-контейнер можно использовать как центральный реестр сервисов:

    • UserService (данные пользователя)
    • ApiService (работа с запросами)
    • ThemeService (тема интерфейса)

    Вместо глобальных переменных/хаков — всё под контролем.

  6. Удобная интеграция в архитектуре (Clean/FSD/MVVM)

    DI отлично ложится в архитектурные подходы:

    • ViewStoreServiceRecord (API)

    Компонент просто запрашивает Store/Service из контейнера и не думает, «как он создаётся».

В итоге:

  • В маленьких проектах DI может быть избыточен.
  • В средних и больших фронтенд-проектах DI упрощает тестирование, модульность и рефакторинг.

2023-2025 Power tool for creating project with Enjoy!