PassFront
  1. PassFront
  2. База знаний
  3. SOLID во фронтенде

Как применить принцип инверсии зависимостей во фронтенде: абстракция над API-клиентом и хранилищем?

SOLID во фронтенде

DIP во фронтенде: компоненты и хуки зависят от интерфейса вроде `UserApi` или `KeyValueStorage`, а конкретный fetch, axios или localStorage подставляется в корне приложения — через контекст или параметры.

Без DIP

В компоненте axios.get('/api/users') и localStorage.getItem('token'): для теста нужен jest.mock по пути модуля, при SSR падает localStorage is not defined, переезд на GraphQL затрагивает десятки файлов.

С DIP

/** @typedef {{ getUser(id: string): Promise<{ id: string, name: string }> }} UserApi */

const ApiContext = createContext(null);

/** @returns {UserApi} */
function useUserApi() {
  const api = useContext(ApiContext);
  if (!api) throw new Error('ApiContext не задан');
  return api;
}

/** @type {UserApi} */
const httpUserApi = {
  getUser: id => fetch(`/api/users/${id}`).then(r => r.json()),
};

const root = <ApiContext.Provider value={httpUserApi}><App /></ApiContext.Provider>;

В тестах и Storybook передают { getUser: async () => fakeUser }.

Что ещё абстрагировать

  • Хранилище: реализации на localStorage, in-memory для SSR и тестов, IndexedDB.
  • Время, аналитику, фича-флаги, логгер.

Когда лишнее

React Query или SWR уже дают слой кэша, а MSW перехватывает запросы на сетевом уровне — часто этого хватает, и свой контекст ради одного эндпоинта избыточен.

Коротко для собеседования

Компоненты зависят от интерфейса вроде UserApi, а конкретную реализацию на fetch или localStorage подставляют в корне через контекст. Это даёт простые подмены в тестах и Storybook и безопасный SSR, но если хватает React Query и MSW, отдельный слой ради одного эндпоинта не нужен.

Другие вопросы раздела