Dependency Injection (DI) — это подход к управлению зависимостями компонентов, который позволяет отделять создание объектов от их использования. В контексте Lit этот подход обеспечивает гибкость, тестируемость и упрощает повторное использование компонентов. В Lit DI реализуется через свойства компонентов, сервисы и провайдеры контекста.
В Lit компоненты наследуются от LitElement. Одним из
способов внедрения зависимостей является использование свойств
(@property) для передачи необходимых объектов:
import { LitElement, html, css } from 'lit';
import { property } from 'lit/decorators.js';
class UserProfile extends LitElement {
@property({ type: Object }) userService;
render() {
if (!this.userService) {
return html`<p>Сервис не подключен</p>`;
}
const user = this.userService.getCurrentUser();
return html`<p>Имя пользователя: ${user.name}</p>`;
}
}
customElements.define('user-profile', UserProfile);
В этом примере userService передаётся как зависимость
извне, что позволяет тестировать компонент с разными реализациями
сервиса без изменения кода компонента.
Lit поддерживает контекст через экспериментальные
API @lit-labs/context, которые упрощают DI для глубоких
деревьев компонентов. Контекст позволяет передавать зависимости вниз по
иерархии без явного пробрасывания через свойства каждого компонента.
Пример создания контекста:
import { createContext, provide, consume } from '@lit-labs/context';
const UserServiceContext = createContext('user-service');
class AppRoot extends LitElement {
userService = new UserService();
render() {
return html`
${provide(this, UserServiceContext, this.userService)}
<user-profile></user-profile>
`;
}
}
class UserProfile extends LitElement {
@consume({ context: UserServiceContext, subscribe: true })
userService;
render() {
return html`<p>Имя пользователя: ${this.userService.getCurrentUser().name}</p>`;
}
}
Ключевые моменты:
createContext создаёт уникальный контекст для
сервиса.provide передаёт сервис вниз по дереву
компонентов.@consume автоматически подписывается на изменения
контекста и обновляет компонент при смене значения.Для сложных зависимостей удобно использовать сервисные классы и фабрики. Сервисный класс инкапсулирует логику, а фабрика создаёт экземпляры на основе конфигурации.
class ApiService {
constructor(baseUrl) {
this.baseUrl = baseUrl;
}
async fetchData(endpoint) {
const response = await fetch(`${this.baseUrl}/${endpoint}`);
return response.json();
}
}
// Фабрика
function createApiService() {
return new ApiService('https://api.example.com');
}
// Использование в компоненте
class DataViewer extends LitElement {
@property({ type: Object }) apiService = createApiService();
async firstUpdated() {
this.data = await this.apiService.fetchData('users');
}
render() {
return html`<pre>${JSON.stringify(this.data, null, 2)}</pre>`;
}
}
Такой подход позволяет легко заменять сервисы на mock-объекты для тестирования.
Иногда удобно передавать зависимости через конструктор:
class Logger {
log(message) {
console.log(message);
}
}
class LogViewer extends LitElement {
constructor(logger) {
super();
this.logger = logger;
}
connectedCallback() {
super.connectedCallback();
this.logger.log('Компонент подключен');
}
}
Метод полезен для компонентов, которые создаются программно, а не через HTML-разметку.
В Lit можно реализовать реактивное обновление зависимостей через свойства с наблюдением:
@property({ type: Object }) userService;
updated(changedProperties) {
if (changedProperties.has('userService')) {
this.user = this.userService.getCurrentUser();
}
}
Это позволяет компоненту динамически реагировать на смену сервисов без полного пересоздания.
DI в Lit упрощает масштабирование приложений, делает компоненты тестируемыми и гибкими. Сочетание свойств, контекста и сервисных классов позволяет организовать чистую архитектуру без жёсткой связности между компонентами и их зависимостями.