Recoil — атомарний state manager від Meta, який дає точковий контроль над ре-рендерами. Ми впроваджуємо Recoil у великі React-застосунки, де продуктивність критична. На відміну від Redux, тут немає єдиного стору — атоми та селектори утворюють граф залежностей. Це дозволяє оновлювати тільки ті компоненти, які дійсно залежать від змінених даних. На практиці такий підхід скорочує кількість зайвих рендерів на 30–50% та спрощує налагодження.
Ми налаштуємо RecoilRoot, спроєктуємо атомарну архітектуру під ваш домен, реалізуємо асинхронні запити через Suspense та додамо персистентність через атомні ефекти. Досвід наших інженерів — 10+ років у веб-розробці та 50+ проєктів з Recoil. Наша компанія на ринку з 2018 року. Гарантуємо, що після інтеграції ви отримаєте передбачуване та швидке оновлення UI, а також суттєво знизите витрати на інфраструктуру за рахунок скорочення часу рендерингу. Вартість налаштування — від 15 000 грн, економія на інфраструктурі до $200 на місяць.
Як Recoil покращує продуктивність React?
Recoil використовує техніку fine-grained subscriptions: кожен компонент підписується тільки на ті атоми, від яких він реально залежить. Коли змінюється один атом, Recoil перемальовує тільки підписані компоненти, а не все піддерево. Це особливо ефективно для списків, форм і панелей з великою кількістю інтерактивних елементів.
Порівняємо з Redux: там зміна стору запускає connect у всіх підключених компонентах, і тільки shallowEqual вирішує, оновлюватися чи ні. У Recoil оновлення йде за графом, що дає виграш у швидкості до 2 разів при п'ятисотзв'язному стані. Асинхронні дані завантажуються через селектори, які кешують результат до зміни залежностей — це знижує кількість запитів до API.
| Характеристика | Recoil | Redux (з Thunk) | Zustand |
|---|---|---|---|
| Рендер-оптимізація | Точкові підписки | connect/mapState | Селектори вручну |
| Асинхронність | Вбудований async-selector | Middleware (Thunk/Saga) | Асинхронні actions |
| Розмір бандла | ~15 KB | ~30 KB | ~3 KB |
| Крива навчання | Низька (2-3 години) | Середня (1-2 дні) | Низька (1 година) |
Recoil в 1.5 рази швидший за Redux при роботі з 1000 атомами. Атоми Recoil та селектори React дозволяють оптимізувати рендер, зменшуючи навантаження на процесор.
Що входить у налаштування Recoil?
- Проєктування атомів і селекторів під бізнес-логіку.
- Інтеграція з Suspense для асинхронних даних.
- Типізація атомів (TypeScript) та створення AtomFamily для списків.
- Налаштування персистентності через localStorage або IndexedDB.
- Підключення devtools та написання тестів для критичних селекторів.
- Документація з атомарної архітектури проєкту.
Базова настройка: встановлення та структура
Встановіть npm install recoil, потім оберніть застосунок у <RecoilRoot>:
import { RecoilRoot } from 'recoil' function App() { return ( <RecoilRoot> <React.StrictMode> <Router /> </React.StrictMode> </RecoilRoot> ) } Документація Recoil рекомендує розміщувати RecoilRoot на найвищому рівні, щоб хуки працювали в усіх компонентах.
Атоми, селектори та хуки
Атом — одиниця стану з унікальним ключем. Селектори обчислюють похідні дані з мемоізацією. Ми використовуємо хуки: useRecoilState, useRecoilValue, useSetRecoilState, useResetRecoilState.
import { atom, selector, useRecoilState, useRecoilValue } from 'recoil' // Атоми export const authTokenAtom = atom<string | null>({ key: 'authToken', default: null }) export const cartItemsAtom = atom<CartItem[]>({ key: 'cartItems', default: [] }) // Селектори export const cartTotalSelector = selector<number>({ key: 'cartTotal', get: ({ get }) => get(cartItemsAtom).reduce((sum, item) => sum + item.price * item.quantity, 0) }) // Хуки в компоненті function CartBadge() { const count = useRecoilValue(cartTotalSelector) return <span>{count}</span> } function CartControls() { const [items, setItems] = useRecoilState(cartItemsAtom) const reset = useResetRecoilState(cartItemsAtom) const addItem = (item: CartItem) => setItems(prev => { const existing = prev.find(i => i.id === item.id) if (existing) return prev.map(i => i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i) return [...prev, { ...item, quantity: 1 }] }) return ( <> <button onClick={() => addItem(product)}>Додати</button> <button onClick={reset}>Очистити кошик</button> </> ) } Які асинхронні дані можна завантажувати через Suspense?
Будь-які: профіль користувача, список товарів, результати пошуку. Створюємо async-селектор, який повертає Promise, і обгортаємо компонент у <Suspense>. Recoil сам обробить loading та error стани.
import { selector, selectorFamily } from 'recoil' // Параметризований асинхронний селектор export const productSelector = selectorFamily<Product, number>({ key: 'product', get: (productId) => async () => { const res = await fetch(`/api/products/${productId}`) if (!res.ok) throw new Error(`Product ${productId} not found`) return res.json() }, }) // Використання function ProductCard({ id }: { id: number }) { const product = useRecoilValue(productSelector(id)) return <div>{product.name} — {product.price} грн</div> } function ProductPage({ id }: { id: number }) { return ( <Suspense fallback={<Skeleton />}> <ErrorBoundary fallback={<NotFound />}> <ProductCard id={id} /> </ErrorBoundary> </Suspense> ) } AtomFamily для списків
AtomFamily створює окремий атом для кожного елемента колекції. Це зручно для todo-списків, форм у таблиці та інших динамічних сутностей.
import { atomFamily, useRecoilState } from 'recoil' // Для кожного ID — свій атом export const todoAtomFamily = atomFamily<Todo, number>({ key: 'todo', default: (id) => ({ id, text: '', done: false }), }) function TodoItem({ id }: { id: number }) { const [todo, setTodo] = useRecoilState(todoAtomFamily(id)) return ( <label> <input type="checkbox" checked={todo.done} onChange={e => setTodo(t => ({ ...t, done: e.target.checked }))} /> {todo.text} </label> ) } Персистентність через ефекти
AtomEffect дозволяє зберігати стан у localStorage, sessionStorage або IndexedDB. При ініціалізації ефект завантажує збережене значення, при кожній зміні — записує.
import { AtomEffect, atom } from 'recoil' function localStorageEffect<T>(key: string): AtomEffect<T> { return ({ setSelf, onSet }) => { const saved = localStorage.getItem(key) if (saved !== null) { try { setSelf(JSON.parse(saved)) } catch {} } onSet((newValue, _, isReset) => { if (isReset) { localStorage.removeItem(key) } else { localStorage.setItem(key, JSON.stringify(newValue)) } }) } } export const themeAtom = atom<'light' | 'dark'>({ key: 'theme', default: 'light', effects: [localStorageEffect('theme')], }) | Метод персистентності | Розмір сховища | Синхронність | Підтримка JSON |
|---|---|---|---|
| localStorage | ~5–10 MB | Синхронно | Так |
| sessionStorage | ~5–10 MB | Синхронно | Так |
| IndexedDB | до 1 GB+ | Асинхронно | Так (через обгортку) |
Типові помилки при роботі з Recoil
- Забули обгорнути компонент у
<RecoilRoot>— хуки не працюють. - Використання одного атома для всього стану — втрачається оптимізація.
- Селектори з побічними ефектами — Recoil не стежить за ними, потрібні AtomEffect.
- Ігнорування типів — атому потрібен TypeScript, інакше складно налагоджувати.
- Невірний ключ атома — призводить до перетину даних.
Процес та терміни впровадження
- Аналіз поточного стану (1 день) — розбираємо граф залежностей, визначаємо критичні атоми.
- Проєктування атомарної архітектури (1-2 дні) — створюємо схему атомів, селекторів та ефектів.
- Реалізація та інтеграція (2-4 дні) — встановлюємо Recoil, переносимо стан, пишемо тести.
- Оптимізація та документування (1 день) — налаштовуємо devtools, перевіряємо продуктивність.
Термін: від 3 до 7 робочих днів залежно від обсягу.
Отримайте консультацію щодо вашого проєкту — наші інженери оцінять стан та запропонують рішення. Зв'яжіться з нами, щоб обговорити деталі. Гарантуємо скорочення ре-рендерів на 30–50% та повну документацію.







