09.05.2025, Ссылка на статью

В мире разработки на React’е мы очень часто пишем компоненты, которые тесно связаны, но потом обнаруживаем, что такие компоненты тяжело тестировать, поддерживать и изменять.

Статья предлагает рассматривать инверсию зависимостей, чтобы избавиться от таких проблем, как:

  • тесная связь кода с API-слоём;
  • сложность тестирования вызовов API;
  • трудности в изменении источника данных;
  • сложности с пониманием состояния запросов и т.д.

Для использования принципа инверсии зависимостей необходимо убедится в том, что модули разного уровня должны зависеть от интерфейсов, а не конкретных реализаций:

interface UserRepository {
	getUser: () => Promise<User>;
}
 
class ApiUserRepository implements UserRepository {
	async getUser(): Promise<User> {
		const response = await fetch('/api/user');
		return response.json();
	}
}
 
class MockUserRepository implements UserRepository {
	private userPromiseWithResolvers?: PromiseWithResolvers<User>;
 
	getUser(): Promise<User> {
		this.userPromiseWithResolvers = Promise.withResolvers();
		return this.userPromiseWithResolvers.promise;
	}
 
	// Метод для резолва промиса
	resolveWithUser(user: User) {
		this.userPromiseWithResolvers?.resolve(user);
	}
 
	// Метод для реджекта промиса
	rejectUser(error: Error) {
		this.userPromiseWithResolvers?.reject(error);
	}
}

Классы, которые имплементируют интерфейс для работы с API-слоём очень легко использовать, тестировать, поддерживать и изменять, т.к. компоненты будут принимать в пропах экземпляры таких интерфейсов.

Если придерживаться функционального подхода, то можно создать хуки, которые внутри себя уже должны работать с интерфейсами абстракций, что реализовать на практике намного сложнее, чем классовый подход.

Из статьи можно вынести лучшие практики:

  • необходимо определять четкие интерфейсы, представляющие зависимости в проекте;
  • использовать экземпляры интерфейсов через пропы или контекст (или через тот самый DI – TSyringe);
  • писать легко тестируемые компоненты с изолированными тестами.