Питання на співбесіді про Zustand 2026: Управління станом у React та найкращі практики

Підготуйтеся до співбесіди з питань про Zustand у 2026 році. Вивчіть управління станом у React, порівняння з Context API, middleware та патерни TypeScript.

Питання на співбесіді про Zustand 2026: Управління станом у React та найкращі практики

Zustand став бібліотекою першого вибору для управління станом у React-додатках, яким потрібно більше, ніж Context API, але менше церемоній, ніж Redux. Завдяки мінімалістичному API та дизайну, орієнтованому на TypeScript, питання про Zustand тепер регулярно з'являються на співбесідах для frontend-розробників, особливо на позиціях mid-level та senior.

На що звертають увагу інтерв'юери

Питання про Zustand на співбесідах тестують три області: розуміння патерну store порівняно з вбудованим станом React, знання того, коли Zustand перевершує альтернативи, та здатність структурувати stores для підтримуваності.

Базові концепції Zustand, які повинен знати кожен кандидат

Zustand v5, випущений наприкінці 2024 року, вніс кілька важливих змін порівняно з v4. Функція create більше не вимагає окремого виклику useStore, а middleware persist тепер за замовчуванням використовує синхронну модель гідратації. Інтерв'юери очікують, що кандидати знають актуальне API.

Що відрізняє Zustand від useState та useReducer?

Store Zustand існують поза деревом компонентів React. Ця архітектурна різниця має три практичні наслідки:

  1. Стан зберігається між монтуванням та розмонтуванням компонентів без потреби в Context providers
  2. Оновлення не викликають повторного рендерингу батьківських компонентів, тільки підписані компоненти перерендерюються
  3. Стан може бути доступний синхронно поза React, що корисно для event handlers та асинхронної логіки
store.tstypescript
import { create } from 'zustand'

interface CartStore {
  items: CartItem[]
  addItem: (item: CartItem) => void
  clearCart: () => void
  totalPrice: () => number
}

export const useCartStore = create<CartStore>((set, get) => ({
  items: [],
  addItem: (item) => set((state) => ({ 
    items: [...state.items, item] 
  })),
  clearCart: () => set({ items: [] }),
  totalPrice: () => get().items.reduce((sum, item) => sum + item.price, 0)
}))

Функція get забезпечує синхронний доступ до поточного стану, уникаючи проблеми stale closure, типової для useState.

Як Zustand обробляє повторний рендеринг?

Zustand за замовчуванням використовує shallow equality. Коли компонент підписується на store через useCartStore(), він отримує весь об'єкт стану і перерендерюється при будь-якій зміні. Вибір конкретних slice запобігає непотрібним рендерам:

Component.tsxtypescript
// Погано: перерендерюється при будь-якій зміні store
const { items } = useCartStore()

// Добре: перерендерюється тільки коли items змінюються
const items = useCartStore((state) => state.items)

// Декілька значень: використовуйте shallow порівняння
import { shallow } from 'zustand/shallow'

const { items, totalPrice } = useCartStore(
  (state) => ({ items: state.items, totalPrice: state.totalPrice() }),
  shallow
)

Shallow comparator з zustand/shallow виконує порівняння посилань на властивостях об'єкта, а не на самому об'єкті.

Zustand vs Context API: Коли обирати що

Це порівняльне питання з'являється майже на кожній співбесіді, що стосується управління станом. Відповідь залежить від частоти оновлень та складності стану.

КритерійContext APIZustand
Розмір bundle0 KB (вбудований)1.2 KB gzipped
Контроль рендерингуРучний з memo/useMemoВбудовані селектори
DevToolsТільки React DevToolsСумісний з Redux DevTools
Server componentsПовна підтримкаПотрібна межа клієнта
Асинхронний станПотрібен wrapperНативна підтримка

Context API добре працює для значень, що рідко змінюються, як-от тема чи locale. Zustand переважає в ситуаціях, коли оновлення стану часті, наприклад inputs форм, дані в реальному часі чи кошики покупок.

Врахування Server Components

Store Zustand вимагають директиви "use client". Для додатків, що інтенсивно використовують React Server Components, це означає, що стан Zustand живе тільки в клієнтських компонентах. Гібридні архітектури часто передають дані, отримані з сервера, як props до клієнтських компонентів, які потім синхронізуються з Zustand.

Патерни middleware в Zustand

Питання про middleware тестують, чи розуміє кандидат композицію замість конфігурації. Middleware Zustand обгортає функцію створення store, додаючи поведінку без модифікації базового API.

Як працює middleware persist?

store.tstypescript
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'

interface UserPreferences {
  theme: 'light' | 'dark'
  language: string
  setTheme: (theme: 'light' | 'dark') => void
}

export const usePreferencesStore = create<UserPreferences>()(
  persist(
    (set) => ({
      theme: 'light',
      language: 'en',
      setTheme: (theme) => set({ theme })
    }),
    {
      name: 'user-preferences',
      storage: createJSONStorage(() => localStorage),
      partialize: (state) => ({ theme: state.theme, language: state.language })
    }
  )
)

Опція partialize виключає функції та похідний стан з персистентності. Без неї серіалізація завершується помилкою або storage роздувається непотрібними даними.

Комбінування декількох middleware

Middleware компонується зсередини назовні. Найбільш внутрішній middleware виконується першим:

store.tstypescript
import { create } from 'zustand'
import { devtools, persist, subscribeWithSelector } from 'zustand/middleware'
import { immer } from 'zustand/middleware/immer'

export const useStore = create<StoreState>()(
  devtools(
    persist(
      subscribeWithSelector(
        immer((set) => ({
          // стан та дії
        }))
      ),
      { name: 'app-storage' }
    ),
    { name: 'AppStore' }
  )
)

Middleware immer дозволяє мутабельний стиль оновлень, що продукують іммутабельний стан. Middleware subscribeWithSelector дозволяє підписуватися на slice стану поза компонентами React.

Готовий до співбесід з React / Next.js?

Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.

Патерни інтеграції TypeScript

Zustand v5 значно покращив виведення типів TypeScript. Патерн подвійного виклику функції create<Type>()() забезпечує повне виведення типів для ланцюжків middleware.

Типізація дій, що посилаються на інший стан

store.tstypescript
interface TodoStore {
  todos: Todo[]
  filter: 'all' | 'active' | 'completed'
  addTodo: (text: string) => void
  toggleTodo: (id: string) => void
  filteredTodos: () => Todo[]
}

export const useTodoStore = create<TodoStore>()((set, get) => ({
  todos: [],
  filter: 'all',
  
  addTodo: (text) => set((state) => ({
    todos: [...state.todos, { id: crypto.randomUUID(), text, completed: false }]
  })),
  
  toggleTodo: (id) => set((state) => ({
    todos: state.todos.map((todo) =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    )
  })),
  
  filteredTodos: () => {
    const { todos, filter } = get()
    switch (filter) {
      case 'active': return todos.filter((t) => !t.completed)
      case 'completed': return todos.filter((t) => t.completed)
      default: return todos
    }
  }
}))

Похідний стан як функції замість getters уникає застарілих значень. Кожен виклик filteredTodos() читає актуальний стан.

Розділення stores для великих додатків

Поширене питання на співбесіді стосується структурування Zustand для масштабування. Патерн slice розділяє store на доменні модулі:

slices/authSlice.tstypescript
import { StateCreator } from 'zustand'

export interface AuthSlice {
  user: User | null
  isAuthenticated: boolean
  login: (credentials: Credentials) => Promise<void>
  logout: () => void
}

export const createAuthSlice: StateCreator<
  AuthSlice & CartSlice,  // Об'єднаний тип store
  [],
  [],
  AuthSlice
> = (set) => ({
  user: null,
  isAuthenticated: false,
  login: async (credentials) => {
    const user = await authApi.login(credentials)
    set({ user, isAuthenticated: true })
  },
  logout: () => set({ user: null, isAuthenticated: false })
})
store.tstypescript
import { create } from 'zustand'
import { createAuthSlice, AuthSlice } from './slices/authSlice'
import { createCartSlice, CartSlice } from './slices/cartSlice'

export const useStore = create<AuthSlice & CartSlice>()((...args) => ({
  ...createAuthSlice(...args),
  ...createCartSlice(...args)
}))

Slices можуть посилатися один на одного через об'єднаний тип store, переданий до StateCreator.

Популярні питання на співбесіді з модельними відповідями

Коли Zustand буде поганим вибором?

Zustand додає складність без користі в цих сценаріях:

  • Статичні конфігураційні дані, що ніколи не змінюються під час виконання
  • Стан форми, керований React Hook Form або подібними бібліотеками
  • Серверний стан, краще керований TanStack Query або SWR
  • Просте передавання props parent-child з 2-3 рівнями вкладеності

Як тестувати компоненти, що використовують Zustand?

Тестування вимагає скидання стану store між тестами та опціонально повного mock store:

store.test.tstypescript
import { act, renderHook } from '@testing-library/react'
import { useCartStore } from './store'

beforeEach(() => {
  // Скидання store до початкового стану
  useCartStore.setState({ items: [], totalItems: 0 })
})

test('addItem збільшує кількість у кошику', () => {
  const { result } = renderHook(() => useCartStore())
  
  act(() => {
    result.current.addItem({ id: '1', name: 'Product', price: 10 })
  })
  
  expect(result.current.items).toHaveLength(1)
})

Для тестів компонентів, які не повинні взаємодіяти з реальними stores, використовуйте mock на рівні модуля:

Component.test.tsxtypescript
import { vi } from 'vitest'

vi.mock('./store', () => ({
  useCartStore: vi.fn(() => ({
    items: [{ id: '1', name: 'Mock Product', price: 25 }],
    addItem: vi.fn()
  }))
}))

Як Zustand обробляє асинхронні операції?

На відміну від Redux, Zustand не вимагає middleware для async. Дії можуть бути безпосередньо async функціями:

store.tstypescript
interface ProductStore {
  products: Product[]
  isLoading: boolean
  error: string | null
  fetchProducts: () => Promise<void>
}

export const useProductStore = create<ProductStore>((set) => ({
  products: [],
  isLoading: false,
  error: null,
  
  fetchProducts: async () => {
    set({ isLoading: true, error: null })
    try {
      const products = await productApi.getAll()
      set({ products, isLoading: false })
    } catch (err) {
      set({ error: err.message, isLoading: false })
    }
  }
}))

Для складних асинхронних потоків рекомендується тримати стани loading та error разом з даними, які вони описують.

Зміни в Zustand v5, які можуть з'явитися на співбесідах

Zustand v5 вніс важливі зміни, які інтерв'юери використовують для оцінки актуальності знань кандидатів:

  • Немає експортів за замовчуванням: Імпортуйте { create } замість create
  • Суворіший TypeScript: Патерн create<T>()() замінює create<T>()
  • Синхронна гідратація persist: Змінено тайминг callback onRehydrateStorage
  • Видалено підтримку CJS: Тільки ESM пакет

Міграція з v4 на v5 вимагає оновлення інструкцій імпорту та налаштування коду, який покладався на старий тайминг гідратації.

Ключові висновки для співбесід про Zustand

  • Store Zustand живуть поза деревом React, забезпечуючи синхронний доступ та запобігаючи вкладенню providers
  • Селектори з shallow порівнянням оптимізують повторний рендеринг для production додатків
  • Middleware persist вимагає partialize для виключення функцій зі storage
  • Патерн slice масштабує Zustand для великих додатків без втрати типобезпеки
  • Async дії працюють безпосередньо без middleware, на відміну від патернів Redux
  • Тестування вимагає явного скидання стану між тестовими випадками
  • Server Components примушують Zustand до клієнтських меж, впливаючи на архітектурні рішення

Починай практикувати!

Перевір свої знання з нашими симуляторами співбесід та технічними тестами.

Щоденний виклик

Чи знайдеш ти помилку в React / Next.js?

Справжній фрагмент коду, прихована помилка, одна спроба на день. Щоб спробувати, акаунт не потрібен.

Anthony Fillion-Maillet

Автор:

Anthony Fillion-Maillet

Засновник SharpSkill

Fullstack-розробник понад 10 років. Керує SharpSkill і відповідає за все, що тут публікується.

Оновлено 25 серпня 2026 р.

Поділитися

Пов'язані статті