Головна / Статті

Контекст, стан чи Redux Toolkit: виберіть залежно від складності переходу

Вибирайте інструменти для керування станом у React залежно від складності переходів, а не розміру додатку: Context поширює залежності деревоподібно, Zustand спрощує підписки, а Redux Toolkit моделює переходи.

Видавець: REACTAPP.TOP4 хв читання1017 слів

Джерела: Vibe Coding Can Build the Store. Can You Design It?

Штучний інтелект зробив одну з частин роботи над фронтендом майже безкоштовною – створення коду. Якщо ви попросите зберігач Zustand, частину Redux або Context разом із useReducer, модель видасть щось, що можна скомпілювати. Синтаксис більше не є складною частиною. Складністю залишається порожній ескіз архітектури та питання про те, як стан має передаватися через продукт.

Ось у чому справжня проблема керування станом. Context API, Zustand та Redux Toolkit підходять до неї з різних боків. Тут не йдеться про дрібниці API – мова про те, що знаходиться під капотом.

1. Context API: дерево React є межею

Context відповідає на конкретне питання: як значення може дістатися до глибоких компонентів, не проходячи через кожен рівень за допомогою prop-drilling?

У документації React useContext описується як засіб для читання та відстеження контексту. Компонент отримує значення від найближчого відповідного постачальника над собою. Коли значення цього постачальника змінюється, значення для компонента оновлюються.

Важливою частиною є не виклик хука — це деревоподібні взаємозв’язки.

Постачальник визначає середовище; споживач читає дані з нього.

Контекст існує в ієрархії React.

Контекст сам по собі не керує станом

Поширеним хибним уявленням є те, що Context — це бібліотека для керування станом. Оновлення зазвичай надходять від useState, редукторів або користувацьких хуків. Context переважно розповсюджує будь-які значення, які генерують ці хуки.

Величезна пастка контексту

Люди кажуть: «Контекст спричиняє зайве перероблення даних». Цей слоган занадто грубий. React порівнює попереднє та наступне значення value у провайдері за допомогою функції Object.is. Коли значення змінюється, оновлюється кожен читач; межа memo не заважає новому контексту.

Розгляньмо набір полів, які не пов’язані між собою:

<AppContext
    value={{
        user,
        cart,
        orders,
        notifications
    }}
>

Глибша проблема є архітектурною: одна широка межа залежностей для доменів, які не належать до одного цілого.

Замість того, щоб:

AppContext

краще:

AuthContext
ThemeContext
FeatureFlagContext

Тоді кожен контекст має чітке семантичне призначення. Правило проектування: контекст має представляти значущу межу залежностей. Контекст не є «поганим у масштабуванні» — часто проблема в гігантському контексті.

2. Залиште дерево: Zustand

Контекст пов’язаний із деревом компонентів. Zustand ставить інше запитання: як може спільний стан клієнта існувати поза цим деревом з точними підписками?

Сховище може зберігати стан та функції, які його змінюють:

const useCartStore = create((set) => ({
    items: [],
addItem: (item) =>
        set((state) => ({
            items: [...state.items, item]
        })),
    removeItem: (id) =>
        set((state) => ({
            items: state.items.filter(
                item => item.id !== id
            )
        }))
}));

Компоненти вибирають потрібні частини:

const items = useCartStore(
    state => state.items
);

Перевагою є межа підписки. Компонент не просить усе сховище; він просить лише ту частину, яка його цікавить. Селектори уточнюють цей контракт.

Чому Redux все ще використовується

Якщо Zustand настільки простий, чому потрібен Redux Toolkit? Тому що проблема може перестати бути «де мені покласти це значення?» та стати «як мені мислити про все, що може його змінити?»

Уявіть собі перекриваючіся домени:

Checkout
Payment
Inventory
Orders
Refunds
Permissions

Робочий процес може виглядати так:

Checkout Started
      ↓
Payment Started
      ↓
Payment Succeeded
      ↓
Inventory Reserved
      ↓
Order Created

Якщо бронювання інвентаря зазнає невдачі після успішної оплати, проблема вже не у зберіганні — це проблема зміни стану. Саме тут структура Redux виявляється корисною.

3. Redux як чіткі переходи

Корисний підхід — це не «Redux — це глобальний магазин даних». Розглядайте його як чітку систему для опису подій та обчислення передбачуваних наступних станів:

nextState =
    reducer(previousState, action)

Потік Redux

Поруч із однорядковою командою set(...) це виглядає складніше:

set(...)

Кожен крок все ще має свою функцію:

  • Дія — що сталось?
  • Редюсер — як має змінитися стан?
  • Магазин даних — що є зараз?
  • Селектор — що потрібно цьому компоненту?

Саме ця чіткість є ключовою особливістю. Коли бізнес-правила ускладнюються, розуміння чому змінився стан може бути кориснішим, ніж збереження десяти рядків коду.

Redux Toolkit — це сучасний Redux

У класичному Redux потрібні були ручні типи дій та їхні функції-створювачі. Toolkit усуває це за допомогою createSlice:

const cartSlice = createSlice({
    name: "cart",
initialState: {
        items: []
    },
    reducers: {
        addItem(state, action) {
            state.items.push(action.payload);
        }
    }
});

Пов’язаний стан та переходи залишаються разом; генеруються функції-створювачі дій. Immer дозволяє використовувати синтаксис, схожий на мутації, при цьому створюючи незмінні оновлення. Сучасний Redux — це менше “написання шаблонного коду” та більше “збереження структурованої моделі переходів без історичних формальностей”.

Мідлверк та асинхронність

Мідлверк знаходиться між функцією dispatch та функцією reduce:

Саме тут знаходяться функції логування, аналітики, асинхронні робочі процеси та реакції на певні дії. Toolkit додає засоби допомоги, такі як createAsyncThunk та проміжки-прослуховувачі. Zustand залишає можливість асинхронної організації; Toolkit пропонує більш усталені патерни.

Який вибрати?

Уникайте методу оцінки за розміром:

  • Маленький додаток → Context
  • Середній → Zustand
  • Великий → Redux

Кількість екранів — це поганий показник складності стану. Додаток з сотнею екранів типу CRUD може бути простішим за додаток з десятьма екранами для обробки платежів та дозволів.

Краще зосереджуватися на взаємозв’язках та переходах:

Ментальна модель для пам’яті

Не запам’ятовуйте три логотипи. Запам’ятайте три підходи:

  • Context — розподіляйте залежності через дерево React
  • Zustand — спільний клієнтський сховище з деталізованими підписками поза структурою дерева
  • Redux Toolkit — чіткі події та передбачувані переходи в структурованій архітектурі
  • „Яка бібліотека найкраща?“ — це неправильне перше запитання. Краще: яку складність потрібно керувати? Виберіть абстракцію, яка полегшує розуміння цієї складності, а не ту, що позначена як „сучасна“.

    На практиці багато кодових баз поєднують їх: Context для теми та локалі, Zustand для кошиків та елементів інтерфейсу, Toolkit лише там, де багатоетапні процеси з грошима та запасами вимагають журналу аудиту. Таке поєднання гарне, якщо кожен межовий рівень є навмисним. Проблеми починаються, коли один мега-контекст або одне недиференційоване сховище поглинає всі аспекти через те, що якась порада запропонувала одну бібліотеку для всього.