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

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. Ця архітектурна різниця має три практичні наслідки:
- Стан зберігається між монтуванням та розмонтуванням компонентів без потреби в Context providers
- Оновлення не викликають повторного рендерингу батьківських компонентів, тільки підписані компоненти перерендерюються
- Стан може бути доступний синхронно поза React, що корисно для event handlers та асинхронної логіки
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 запобігає непотрібним рендерам:
// Погано: перерендерюється при будь-якій зміні 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 API | Zustand |
|---|---|---|
| Розмір bundle | 0 KB (вбудований) | 1.2 KB gzipped |
| Контроль рендерингу | Ручний з memo/useMemo | Вбудовані селектори |
| DevTools | Тільки React DevTools | Сумісний з Redux DevTools |
| Server components | Повна підтримка | Потрібна межа клієнта |
| Асинхронний стан | Потрібен wrapper | Нативна підтримка |
Context API добре працює для значень, що рідко змінюються, як-от тема чи locale. Zustand переважає в ситуаціях, коли оновлення стану часті, наприклад inputs форм, дані в реальному часі чи кошики покупок.
Store Zustand вимагають директиви "use client". Для додатків, що інтенсивно використовують React Server Components, це означає, що стан Zustand живе тільки в клієнтських компонентах. Гібридні архітектури часто передають дані, отримані з сервера, як props до клієнтських компонентів, які потім синхронізуються з Zustand.
Патерни middleware в Zustand
Питання про middleware тестують, чи розуміє кандидат композицію замість конфігурації. Middleware Zustand обгортає функцію створення store, додаючи поведінку без модифікації базового API.
Як працює middleware persist?
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 виконується першим:
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.
Типізація дій, що посилаються на інший стан
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 на доменні модулі:
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 })
})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:
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 на рівні модуля:
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 функціями:
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Засновник SharpSkill
Fullstack-розробник понад 10 років. Керує SharpSkill і відповідає за все, що тут публікується.
Оновлено 25 серпня 2026 р.
Поділитися
Пов'язані статті

Next.js 16 Middleware u 2026: Edge Runtime, Avtentyfikatsiia ta Pytannia na Spivbesidi
Kompleksnyi posibnyk z Next.js 16 Middleware: Edge Runtime, paterny avtentyfikatsii ta pytannia na spivbesidi.

React 19 Suspense та Concurrent Rendering: Streaming SSR і питання для співбесід 2026
Повний посібник з React 19 Suspense, concurrent rendering та streaming SSR. Вивчіть use() API, useTransition, useDeferredValue та підготуйтеся до технічних співбесід.

Тестування React у 2026 році: Vitest, React Testing Library та найкращі практики
Повний посібник з тестування React-додатків за допомогою Vitest та React Testing Library. Патерни тестування компонентів, робота з асинхронними операціями, стратегії мокування та практики для технічних співбесід.