Гайды / Работа с DI
В этом разделе описано, как интегрировать Nuxoblivius с контейнерами зависимостей (DI). Для примера мы будем использовать популярный контейнер Inversify.
1. Подготовка
Перед началом работы с Nuxoblivius и интеграцией с DI необходимо выполнить базовую настройку проекта.
Установите зависимости
npm install nuxoblivius inversifyИнициализация контейнера
import { Container } from "inversify";
const container = new Container();2. Создание UserService
Создадим сервис, который будет хранить и управлять данными пользователя. Он будет зарегистрирован как singleton, чтобы одно состояние переиспользовалось во всём приложении.
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);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-контейнер:
import { Container } from "inversify";
import { UserService, useUserService } from "@/service/UserService";
export const container = new Container();
container
.bind(UserService)
.toDynamicValue(() => useUserService())
.inSingletonScope();4. Внедрение зависимостей
После регистрации сервисов и стора в контейнере, их можно внедрять (инъектировать) в другие классы.
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);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);Так-же регистрация в контейнере:
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 Файл
Внедрение работает через контейнер, так-же как и в примерах выше.
<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 используют реже, чем в бэкенде, но он реально даёт плюсы, особенно если проект крупный и модульный. Вот основные плюсы:
Ослабленная связность (Loose Coupling)
Компоненты и сервисы не знают, кто именно создаёт их зависимости. Они просто описывают «мне нужен UserService», а контейнер уже решает, какую реализацию дать. Это снижает «спагетти-зависимости» в коде.
Лёгкое тестирование
С DI можно в контейнер подложить мок или фейковую реализацию.Например, вместо настоящего ApiService в тестах используется FakeApiService, и код компонента менять не нужно.
Гибкая замена реализаций
Если сервис меняется (например, LocalStorageAuthService -> JwtAuthService), то компоненты этого не замечают: достаточно переназначить биндинг в контейнере.
Управление жизненным циклом
DI позволяет контролировать:
- singleton (один на всё приложение),
- transient (новый объект при каждом запросе),
- scoped (на сессию, на страницу и т.д.). Это полезно для сторов, кэшей, сервисов пользователя.
Единый подход к управлению состоянием
DI-контейнер можно использовать как центральный реестр сервисов:
- UserService (данные пользователя)
- ApiService (работа с запросами)
- ThemeService (тема интерфейса)
Вместо глобальных переменных/хаков — всё под контролем.
Удобная интеграция в архитектуре (Clean/FSD/MVVM)
DI отлично ложится в архитектурные подходы:
- View → Store → Service → Record (API)
Компонент просто запрашивает Store/Service из контейнера и не думает, «как он создаётся».
В итоге:
- В маленьких проектах DI может быть избыточен.
- В средних и больших фронтенд-проектах DI упрощает тестирование, модульность и рефакторинг.
