Dependency injection

Dependency Injection (DI) — это паттерн проектирования, который позволяет разделять создание зависимостей и их использование. В контексте Stencil, DI применяется для управления сервисами и компонентами, что упрощает тестирование и повышает модульность приложений.

Основы DI в Stencil

Stencil поддерживает встроенный механизм сервисов, который позволяет регистрировать объекты как зависимости и внедрять их в компоненты. Любой сервис в Stencil — это класс с аннотацией @Injectable().

Пример создания сервиса:

import { Injectable } from '@stencil/core';

@Injectable()
export class LoggerService {
  log(message: string) {
    console.log('[Logger]:', message);
  }
}
  • @Injectable() помечает класс как сервис, который может быть внедрён в компоненты.
  • Методы сервиса доступны любому компоненту, который получит его через DI.

Регистрация сервисов

В Stencil сервисы регистрируются через контейнер зависимостей, который создается автоматически для каждого проекта. При регистрации можно задавать область действия:

  • Singleton — один экземпляр на всё приложение.
  • Transient — новый экземпляр при каждом внедрении.

Пример регистрации:

import { Config, Injectable } from '@stencil/core';

@Injectable({
  scope: 'singleton'
})
export class ApiService {
  fetchData(url: string) {
    return fetch(url).then(res => res.json());
  }
}

scope: 'singleton' гарантирует, что в приложении будет использоваться один объект ApiService.

Внедрение зависимостей в компоненты

Для внедрения зависимости используется декоратор @Inject():

import { Component, h, Inject } from '@stencil/core';
import { LoggerService } from './logger-service';

@Component({
  tag: 'app-logger',
  styleUrl: 'app-logger.css',
  shadow: true
})
export class AppLogger {
  @Inject() private logger: LoggerService;

  componentWillLoad() {
    this.logger.log('Компонент загружается');
  }

  render() {
    return <div>Проверка логирования</div>;
  }
}
  • Декоратор @Inject() автоматически передаёт экземпляр зарегистрированного сервиса.
  • Компонент получает доступ к методам сервиса без необходимости создавать объект вручную.

Внедрение нескольких зависимостей

Stencil поддерживает внедрение нескольких зависимостей одновременно:

import { Component, h, Inject } from '@stencil/core';
import { LoggerService } from './logger-service';
import { ApiService } from './api-service';

@Component({
  tag: 'app-dashboard',
  shadow: true
})
export class AppDashboard {
  @Inject() private logger: LoggerService;
  @Inject() private api: ApiService;

  async componentWillLoad() {
    this.logger.log('Загрузка данных');
    const data = await this.api.fetchData('/api/data');
    this.logger.log('Данные загружены: ' + JSON.stringify(data));
  }

  render() {
    return <div>Дашборд загружен</div>;
  }
}

Использование DI для тестирования

DI позволяет легко подменять реальные сервисы мок-объектами. Например, для тестирования:

class MockApiService {
  fetchData() {
    return Promise.resolve([{ id: 1, name: 'Test' }]);
  }
}

// В компоненте теста можно внедрить MockApiService вместо реального ApiService

Это устраняет необходимость в сетевых запросах при unit-тестах и делает код предсказуемым.

Плюсы DI в Stencil

  • Модульность: компоненты не зависят от конкретной реализации сервисов.
  • Тестируемость: легко заменять зависимости моками.
  • Управление жизненным циклом: контейнер DI контролирует создание и уничтожение объектов.
  • Повторное использование кода: сервисы могут использоваться в нескольких компонентах.

Практические рекомендации

  • Использовать DI для всех сервисов, которые взаимодействуют с API, состоянием приложения или внешними библиотеками.
  • Singleton-сервисы подходят для глобального состояния или кэша.
  • Transient-сервисы — для краткоживущих операций, например, для обработки данных внутри одного компонента.
  • Сохранять сервисы максимально независимыми, чтобы их можно было легко заменять при тестировании или рефакторинге.

Ограничения

  • DI работает только с сервисами, зарегистрированными в контейнере Stencil.
  • Внедрение не поддерживает динамическую регистрацию на лету; все зависимости должны быть известны на этапе сборки.

Stencil предоставляет мощный, но простой механизм Dependency Injection, который обеспечивает чистую архитектуру и упрощает поддержку масштабных приложений.