Dependency Injection (DI) — это шаблон проектирования, который позволяет отделить создание зависимостей от их использования, облегчая тестирование и повышая модульность кода. В контексте Preact, где компоненты тесно связаны с состоянием и жизненным циклом, DI становится особенно полезным для управления сервисами, конфигурациями и внешними зависимостями.
DI строится на трёх ключевых принципах:
Инверсия управления (Inversion of Control, IoC) Компонент не создаёт свои зависимости напрямую. Вместо этого они предоставляются извне, что уменьшает связанность и упрощает замену реализации.
Внедрение зависимостей (Injection) Зависимости могут передаваться через:
Абстракция зависимостей Компоненты работают с интерфейсами или абстракциями, а не с конкретными реализациями, что облегчает модульное тестирование.
Наиболее простой способ внедрения зависимостей в Preact — передача сервисов через пропсы.
import { h } from 'preact';
function LoggerService() {
this.log = (msg) => console.log(`[LOG]: ${msg}`);
}
function App({ logger }) {
logger.log('Приложение инициализировано');
return <div>Привет, Preact!</div>;
}
// Внедрение зависимости при рендере
<App logger={new LoggerService()} />;
Преимущества такого подхода:
Недостатки:
Preact поддерживает контекст через createContext, что
позволяет внедрять зависимости глубоко в иерархию компонентов без
передачи через пропсы на каждом уровне.
import { h, createContext } from 'preact';
import { useContext } from 'preact/hooks';
const LoggerContext = createContext(null);
function App() {
return (
<LoggerContext.Provider value={new LoggerService()}>
<ChildComponent />
</LoggerContext.Provider>
);
}
function ChildComponent() {
const logger = useContext(LoggerContext);
logger.log('ChildComponent загружен');
return <div>Дочерний компонент</div>;
}
Плюсы использования контекста:
Минусы:
Для более сложных приложений часто используют фабрики или сервис-локаторы, которые создают и управляют жизненным циклом зависимостей.
class ServiceFactory {
constructor() {
this.services = new Map();
}
register(key, instance) {
this.services.set(key, instance);
}
get(key) {
return this.services.get(key);
}
}
const factory = new ServiceFactory();
factory.register('logger', new LoggerService());
function App() {
const logger = factory.get('logger');
logger.log('Приложение через фабрику');
return <div>DI с фабрикой</div>;
}
Плюсы:
Минусы:
Компоненты, построенные с хуками, также могут использовать DI. Контекст и фабрики идеально подходят для предоставления зависимостей в функциональные компоненты:
import { useState, useEffect, useContext } from 'preact/hooks';
function useLogger() {
const logger = useContext(LoggerContext);
useEffect(() => {
logger.log('Hook инициализирован');
}, []);
return logger;
}
function HookComponent() {
const logger = useLogger();
return <div>Использование Logger через hook</div>;
}
DI упрощает написание юнит-тестов. Компоненты легко тестировать, подставляя моки вместо реальных сервисов.
const mockLogger = { log: jest.fn() };
<App logger={mockLogger} />;
// Проверка вызова log
expect(mockLogger.log).toHaveBeenCalledWith('Приложение инициализировано');
Использование DI снижает связанность компонентов и делает архитектуру приложения более гибкой. В Preact паттерн помогает управлять сервисами, облегчает тестирование и масштабирование проектов без потери производительности или читаемости кода.