Настройка State Management (Recoil) для React-приложения

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Настройка State Management (Recoil) для React-приложения
Простая
от 4 часов до 2 рабочих дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Настройка State Management (Recoil) для React-приложения

Recoil — атомарный state manager от Meta. Атомы и селекторы образуют граф состояния, который React использует для точечных ре-рендеров. Нативно поддерживает Concurrent Mode, Suspense и асинхронные селекторы.

Подходит для крупных React-приложений с разветвлённым состоянием, где важна гранулярность подписок и встроенная поддержка async без дополнительных библиотек.

Что входит в работу

Инициализация Recoil в проекте, проектирование атомов и селекторов, настройка async-данных через Suspense, типизация, devtools, персистентность, атомные семейства для списков.

Установка

npm install recoil
# devtools
npm install recoil-devtools

Оборачиваем приложение в RecoilRoot:

import { RecoilRoot } from 'recoil'

function App() {
  return (
    <RecoilRoot>
      <Router />
    </RecoilRoot>
  )
}

Атомы

import { atom } from 'recoil'

export const authTokenAtom = atom<string | null>({
  key: 'authToken',
  default: null,
})

export const cartItemsAtom = atom<CartItem[]>({
  key: 'cartItems',
  default: [],
})

export const sidebarOpenAtom = atom<boolean>({
  key: 'sidebarOpen',
  default: false,
})

Ключи атомов — уникальные строки во всём приложении. Удобно использовать префикс по домену: 'cart/items', 'auth/token'.

Селекторы

import { selector } from 'recoil'

export const cartTotalSelector = selector<number>({
  key: 'cartTotal',
  get: ({ get }) => {
    const items = get(cartItemsAtom)
    return items.reduce((sum, item) => sum + item.price * item.quantity, 0)
  },
})

export const cartCountSelector = selector<number>({
  key: 'cartCount',
  get: ({ get }) => get(cartItemsAtom).reduce((sum, i) => sum + i.quantity, 0),
})

Селекторы мемоизированы — пересчитываются только при изменении зависимых атомов.

Хуки

import { useRecoilState, useRecoilValue, useSetRecoilState, useResetRecoilState } from 'recoil'

function CartBadge() {
  const count = useRecoilValue(cartCountSelector)
  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>
    </>
  )
}

Async-селекторы и Suspense

import { selector, selectorFamily } from 'recoil'

// selectorFamily — параметризованный селектор
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()
  },
})

export const currentUserSelector = selector<User | null>({
  key: 'currentUser',
  get: async ({ get }) => {
    const token = get(authTokenAtom)
    if (!token) return null
    const res = await fetch('/api/me', {
      headers: { Authorization: `Bearer ${token}` },
    })
    return res.json()
  },
})
function ProductCard({ id }: { id: number }) {
  const product = useRecoilValue(productSelector(id)) // suspend

  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 — атомы для списков

import { atomFamily } from 'recoil'

export const todoAtomFamily = atomFamily<Todo, number>({
  key: 'todo',
  default: (id) => ({ id, text: '', done: false }),
})

// у каждого todo-айтема — независимый атом
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>
  )
}

Персистентность через effects

import { AtomEffect } 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')],
})

Тестирование

import { renderRecoilHook } from 'recoil-test-utils'

test('cartTotal вычисляется правильно', () => {
  const { result } = renderRecoilHook(
    () => useRecoilValue(cartTotalSelector),
    {
      initializeState: ({ set }) => {
        set(cartItemsAtom, [
          { id: '1', price: 100, quantity: 2 },
          { id: '2', price: 50, quantity: 1 },
        ])
      },
    }
  )
  expect(result.current).toBe(250)
})

Структура файлов

src/
  recoil/
    atoms/
      auth.ts
      cart.ts
      ui.ts
    selectors/
      cart.selectors.ts
      user.selectors.ts
    families/
      products.family.ts
      todos.family.ts

Что делаем

Настраиваем RecoilRoot, проектируем атомный граф под задачи проекта, реализуем async-данные через selectorFamily с Suspense, подключаем atom effects для персистентности, покрываем тестами ключевые селекторы.

Срок: 1–2 дня.