Irbisa · Шпаргалка

Управление состоянием

Локальное, глобальное и серверное состояние, Context и цена его ререндеров, Redux Toolkit, Zustand, Jotai, URL как состояние, выбор стора

Middle-разработчик · 21 вопрос

  1. 01

    Чем отличаются локальное, глобальное и серверное состояние в React-приложении?

    Локальное состояние принадлежит одному компоненту или небольшому поддереву (открытый дропдаун, значение инпута), глобальное клиентское описывает общие данные, которыми владеет клиент (тема, сессия, корзина), а серверное хранит кэшированную копию данных бэкенда и может устареть в любой момент.

  2. 02

    Объясните однонаправленный поток данных в Redux. Почему редьюсеры должны быть чистыми?

    UI диспатчит экшен (обычный объект, который описывает, что произошло), стор вызывает редьюсер (state, action) => newState, а подписанные компоненты заново читают свою часть через селекторы.

  3. 03

    Может ли React Context заменить Redux или другой стейт-менеджер?

    Нет. Context служит механизмом внедрения зависимостей: он передаёт значение вниз по дереву, но ничего не хранит, не обновляет и не оптимизирует.

  4. 04

    Найдите баг: ввод в поле поиска перерендеривает каждый компонент, который читает текущего пользователя.

    export function AppProvider({ children }: { children: React.ReactNode }) {  const [user, setUser] = useState<User | null>(null);  const [query, setQuery] = useState('');   return (    <AppContext value={{ user, setUser, query, setQuery }}>      {children}    </AppContext>  );} function Avatar() {  const { user } = use(AppContext);  return <img src={user?.avatarUrl} alt="" />;}

    Поисковый запрос и пользователь лежат в одном контексте, а значение создаётся новым объектом на каждый рендер, поэтому каждое нажатие клавиши меняет значение контекста и перерендеривает всех потребителей, включая Avatar, хотя user не изменился.

  5. 05

    Какие проблемы решает Redux Toolkit по сравнению с Redux, написанным вручную?

    Redux Toolkit представляет собой официальный способ писать на Redux: configureStore одним вызовом подключает DevTools, thunk-и и проверки для разработки, а createSlice генерирует экшен-криэйторы и редьюсер из одного объекта, причём Immer разрешает мутирующий синтаксис.

    import { configureStore, createSlice, type PayloadAction } from '@reduxjs/toolkit';import { useDispatch, useSelector } from 'react-redux'; const cartSlice = createSlice({  name: 'cart',  initialState: { items: [] as { id: string; qty: number }[] },…
  6. 06

    Найдите баг: диспатч todosCleared запускает редьюсер, но список остаётся на экране.

    const todosSlice = createSlice({  name: 'todos',  initialState: { items: [] as Todo[], filter: 'all' as Filter },  reducers: {    todoAdded(state, action: PayloadAction<Todo>) {      state.items.push(action.payload);    },    todosCleared(state) {      state = { items: [], filter: state.filter };    },  },});

    Редьюсер переприсваивает параметр state вместо того, чтобы изменить драфт или вернуть новое значение.

  7. 07

    Как работают подписки useSelector и почему селектор может вызывать лишние ререндеры?

    useSelector вызывает селектор после каждого экшена и перерендеривает компонент, только если результат изменился по ссылке (===).

    import { createSelector } from '@reduxjs/toolkit';import { useSelector } from 'react-redux'; const selectItems = (state: RootState) => state.todos.items;const selectFilter = (state: RootState) => state.todos.filter;…
  8. 08

    Где в Redux Toolkit живут асинхронная логика и побочные эффекты?

    Не в редьюсерах.

    import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; export const fetchUser = createAsyncThunk('user/fetch', async (id: string, { signal }) => {  const res = await fetch(`/api/users/${id}`, { signal });  if (!res.ok) throw new Error(`HTTP ${res.status}`);  return (await res.json()) as User;…
  9. 09

    Что такое RTK Query и когда выбирать его вместо TanStack Query?

    RTK Query представляет собой слой загрузки и кэширования данных, встроенный в Redux Toolkit: вы описываете эндпоинты в createApi, он генерирует хуки, а кэш живёт в Redux-сторе.

    import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; export const api = createApi({  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),  tagTypes: ['Todo'],  endpoints: (build) => ({…
  10. 10

    Как хранить в Redux связанные данные и списки и что даёт createEntityAdapter?

    Храните коллекции нормализованными: каждая сущность лежит один раз в словаре entities по id, порядок хранит ids, а на другие сущности ссылаются по id.

    import { createEntityAdapter, createSlice, type PayloadAction } from '@reduxjs/toolkit'; interface Post {  id: string;  title: string;  authorId: string; // a reference, not a nested user object…
  11. 11

    Что такое Zustand и чем он отличается от Redux Toolkit?

    Zustand представляет собой маленький стор на хуках: create возвращает хук, состояние и экшены живут в одном объекте, а компоненты подписываются через селектор.

    import { create } from 'zustand'; interface CartState {  items: string[];  add: (id: string) => void;  clear: () => void;…
  12. 12

    Найдите баг: после обновления до Zustand 5 этот компонент падает с ошибкой «Maximum update depth exceeded».

    function CartSummary() {  const { items, clear } = useCart((s) => ({ items: s.items, clear: s.clear }));  return (    <div>      {items.length} items      <button onClick={clear}>Clear</button>    </div>  );}

    Селектор возвращает новый объект при каждом вызове.

  13. 13

    Что такое атомарный подход к состоянию (Jotai) и когда он подходит лучше единого стора?

    В Jotai состояние состоит из многих маленьких атомов, а не из одного дерева: компонент подписывается ровно на те атомы, которые читает, а производные атомы вычисляются из других, как ячейки таблицы.

    import { atom, useAtom, useAtomValue } from 'jotai'; const priceAtom = atom(100);const qtyAtom = atom(1);const totalAtom = atom((get) => get(priceAtom) * get(qtyAtom)); // derived, read-only…
  14. 14

    Что такое MobX и чем его модель наблюдаемого состояния отличается от Redux?

    MobX делает состояние наблюдаемым (observable), запоминает, какие observable-значения каждый компонент читает во время рендера, и перерендеривает ровно эти компоненты, когда значения меняются.

    class CartStore {  items: { id: string; price: number; qty: number }[] = [];   constructor() {    makeAutoObservable(this); // fields: observable, getters: computed, methods: actions  }…
  15. 15

    Как сохранять состояние стора между перезагрузками страницы и что может пойти не так?

    Используйте middleware персистентности, например persist в Zustand или redux-persist: он сохраняет выбранную часть состояния в localStorage (или IndexedDB) и восстанавливает её при старте.

    import { create } from 'zustand';import { persist } from 'zustand/middleware'; type Theme = 'light' | 'dark'; interface PrefsState {…
  16. 16

    Когда состояние стоит хранить в URL, а не в стейте React или в сторе?

    Кладите состояние в URL, когда пользователь ожидает, что им можно поделиться, сохранить в закладки или вернуться к нему: поисковый запрос, фильтры, сортировка, пагинация, выбранная вкладка или открытый элемент.

  17. 17

    Почему копировать ответы API в Redux или Context обычно считается антипаттерном и что использовать вместо этого?

    Серверные данные представляют собой кэш того, чем владеет бэкенд, поэтому им нужны состояния загрузки и ошибки, дедупликация, перезапрос, инвалидация и сборка мусора.

  18. 18

    Как сохранить стор поддерживаемым в большом приложении со множеством фич и команд?

    Делите его по фичам: каждая фича владеет своим слайсом, селекторами и экшенами в своей папке, другие фичи пользуются только её публичными селекторами, а слайсы подключаются, когда загружается код фичи (combineSlices с ленивыми слайсами в Redux Toolkit 2 или отдельные маленькие сторы в Zustand).

  19. 19

    Как бы вы поэтапно перевели большое приложение со старого Redux (connect, саги, редьюсеры, написанные вручную) на Redux Toolkit или TanStack Query?

    Постепенно и никогда не переписывая всё разом: сначала замените createStore на configureStore, чтобы весь старый код без изменений работал в новом сторе, затем перенесите серверные данные в RTK Query или TanStack Query, переводите редьюсеры на createSlice по мере работы над фичами, а connect и саги убирайте в последнюю очередь.

    import { configureStore } from '@reduxjs/toolkit';import createSagaMiddleware from 'redux-saga';import { legacyReducers } from './legacy/reducers'; // hand-written reducers, untouchedimport { rootSaga } from './legacy/sagas';import { cartSlice } from './features/cart/cartSlice'; // migrated to createSliceimport { api } from './services/api'; // RTK Query, filled endpoint by endpoint…
  20. 20

    Когда конечный автомат (XState) моделирует состояние UI лучше, чем булевы флаги в сторе, и чего это стоит?

    Когда у сценария несколько шагов и события, допустимые только в части из них: оформление заказа, загрузка файла с повтором и отменой, соединение с переподключением.

    // flags: { isSubmitting, isError, isSuccess } allow 8 combinations, only 4 make sense type State = 'idle' | 'submitting' | 'failed' | 'succeeded';type Event = 'SUBMIT' | 'RESOLVE' | 'REJECT' | 'RESET'; // one state at a time, and each state lists the events it accepts…
  21. 21

    Как бы вы выбирали подход к управлению состоянием для нового React-приложения?

    Сначала классифицируйте состояние и для каждой категории возьмите самый лёгкий инструмент: локальное в компонентах, серверное в кэше запросов, состояние экрана для ссылок в URL, а стор только для оставшегося клиентского глобального состояния.