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% та повну документацію.







