Skip to main content

Ізоляція API від UI

Що означає "ізолювати шар API від UI"

Ізолювати шар API - означає розділити код, який:

  • спілкується з сервером (HTTP-запити, REST, GraphQL тощо), від коду, який:
  • відображає інтерфейс (React-компоненти, стан, події).

Тобто UI не повинен напряму викликати:

javascript
fetch('/api/users')

або знати, як саме влаштований сервер. Замість цього UI звертається до функції з окремого шару, наприклад:

javascript
// api/users.ts export async function getUsers() { const res = await fetch('/api/users'); return res.json(); }

А в компоненті:

javascript
const { data } = useQuery({ queryKey: ['users'], queryFn: getUsers });

Основна ідея

UI повинен знати лише що потрібно отримати, але не як саме це отримати.

Це - реалізація принципу Separation of Concerns (розділення відповідальності).


Чому це важливо

1. Чиста архітектура і читабельність

Коли API-логіка (запити, хедери, авторизація) змішана з UI-кодом, компоненти стають "брудними" і важкими для читання:

javascript
function UserList() { const [users, setUsers] = useState([]); useEffect(() => { fetch('/api/users', { headers: { Authorization: token } }) .then(res => res.json()) .then(setUsers); }, []); // UI + мережеві деталі впереміш return users.map(u => <div>{u.name}</div>); }

Після ізоляції:

javascript
// api/users.ts export const getUsers = () => api.get<User[]>('/users'); // централізований fetch wrapper // components/UserList.tsx const { data: users } = useQuery({ queryKey: ['users'], queryFn: getUsers });

Компонент займається лише відображенням, а API-код - лише запитами.


2. Переюзабельність

Якщо в кількох місцях потрібно отримати одного й того самого користувача:

javascript
useQuery({ queryKey: ['user', id], queryFn: () => getUser(id) });

ти використовуєш одну й ту саму API-функцію, а не копіюєш fetch у кожному компоненті.


3. Тестованість

Коли API ізольовано, ти можеш:

  • мокати його в тестах;
  • перевіряти UI без реального сервера.

Приклад:

javascript
// jest.setup.ts jest.mock('@/api/users', () => ({ getUsers: jest.fn(() => Promise.resolve([{ id: 1, name: 'Alex' }])), }));

Тести на компонент тепер не залежать від мережі:

javascript
render(<UserList />); expect(screen.getByText('Alex')).toBeInTheDocument();

4. Централізована обробка помилок і авторизації

Коли всі запити йдуть через одну точку (apiClient), можна:

  • автоматично підставляти токен,
  • перехоплювати 401 і оновлювати JWT,
  • логувати помилки,
  • обробляти таймаути.
javascript
// api/client.ts export const api = axios.create({ baseURL: '/api', }); api.interceptors.response.use( res => res, err => { if (err.response?.status === 401) refreshToken(); return Promise.reject(err); } );

Тепер не потрібно повторювати цей код у кожному компоненті.


5. Легка заміна бекенду

Якщо API змінюється (наприклад, REST → GraphQL або новий endpoint), змінюється лише шар API, а UI-код узагалі не чіпається.

javascript
// раніше export const getUser = (id: string) => fetch(`/api/users/${id}`).then(r => r.json()); // потім export const getUser = (id: string) => graphQLClient.request(GET_USER_QUERY, { id });

Компоненти залишаються незмінними:

javascript
const { data } = useQuery({ queryKey: ['user', id], queryFn: () => getUser(id) });

6. Інтеграція з React Query / SWR / Zustand

Сучасні менеджери даних (React Query, SWR) очікують чисту функцію:

javascript
queryFn: () => Promise<Data>

Ізоляція API робить код природним:

javascript
useQuery({ queryKey: ['products'], queryFn: getProducts });

→ React Query сам кешує, оновлює і синхронізує дані.


7. Відповідність архітектурним принципам

Це відповідає принципам:

  • SRP (Single Responsibility Principle) - кожен модуль робить одне;
  • Separation of Concerns - UI не знає про мережеві деталі;
  • Dependency Inversion - React залежить від абстракції (getUser), а не від fetch;
  • Clean Architecture - зовнішній шар (API) ізольований від внутрішнього (UI).

Приклад архітектури

javascript
src/ ├── api/ │ ├── client.ts ← спільне налаштування fetch/axios │ ├── users.ts ← функції API │ └── posts.ts ├── features/ │ ├── users/ │ │ ├── hooks.tsuseUserQuery(), useCreateUserMutation() │ │ ├── components/ │ │ │ └── UserList.tsx │ │ └── index.ts ├── ui/ │ ├── components/ │ └── layout/ └── app.tsx

Уся логіка роботи з сервером - у api/ Компоненти → хуки → API → сервер.


Підсумок

ПеревагаЩо це дає
Розділення відповідальностіUI займається відображенням, API - даними
Централізована логікатокени, помилки, retry в одному місці
Легка зміна бекендуUI не ламається при змінах API
Просте тестуванняможна мокати API
Повторне використанняоднакові виклики з різних місць
Сумісність з React Query / SWRчисті функції без сайд-ефектів

Коротко:

Ізоляція шару API від UI робить застосунок чистішим, надійнішим і простішим у підтримці.

React-компоненти повинні думати лише про те, що показувати, а не як дістати дані.

Усе мережеве, авторизаційне й технічне - виноситься в шар API.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.