Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Ізоляція API від UI». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)Ізолювати шар API - означає **розділити** код, який спілкується з сервером, від коду, який відображає інтерфейс, щоб UI знав лише **що потрібно отримати**, а не **як саме це отримати**. **Ключове:** це реалізація принципу Separation of Concerns - UI займається лише відображенням, а API-код - лише запитами.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення## Що означає "ізолювати шар 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.ts ← useUserQuery(), 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.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.