Pytania rekrutacyjne Zustand 2026: Zarządzanie stanem w React i najlepsze praktyki

Przygotuj się do rozmowy kwalifikacyjnej z pytań o Zustand w 2026 roku. Poznaj zarządzanie stanem w React, porównanie z Context API, middleware oraz wzorce TypeScript.

Pytania rekrutacyjne Zustand 2026: Zarządzanie stanem w React i najlepsze praktyki

Zustand stał się preferowaną biblioteką do zarządzania stanem w aplikacjach React, które wymagają więcej niż Context API, ale mniej ceremonii niż Redux. Dzięki minimalistycznemu API i projektowi zorientowanemu na TypeScript, pytania o Zustand regularnie pojawiają się na rozmowach kwalifikacyjnych dla frontend developerów, szczególnie na stanowiskach mid-level i senior.

Na co zwracają uwagę rekruterzy

Pytania rekrutacyjne o Zustand testują trzy obszary: zrozumienie wzorca store vs wbudowany stan React, wiedzę o tym kiedy Zustand przewyższa alternatywy oraz umiejętność strukturyzacji stores pod kątem utrzymywalności.

Podstawowe koncepcje Zustand, które powinien znać każdy kandydat

Zustand v5, wydany pod koniec 2024 roku, wprowadził kilka przełomowych zmian w stosunku do v4. Funkcja create nie wymaga już oddzielnego wywołania useStore, a middleware persist domyślnie używa synchronicznego modelu hydratacji. Rekruterzy oczekują, że kandydaci znają aktualne API.

Co odróżnia Zustand od useState i useReducer?

Store Zustand istnieją poza drzewem komponentów React. Ta architektoniczna różnica ma trzy praktyczne konsekwencje:

  1. Stan persystuje między montowaniem i odmontowywaniem komponentów bez potrzeby Context providerów
  2. Aktualizacje nie wywołują re-renderów komponentów nadrzędnych, tylko subskrybowane komponenty są ponownie renderowane
  3. Stan może być dostępny synchronicznie poza React, co jest przydatne dla event handlerów i logiki asynchronicznej
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)
}))

Funkcja get zapewnia synchroniczny dostęp do aktualnego stanu, unikając problemu stale closure typowego dla useState.

Jak Zustand obsługuje re-rendery?

Zustand domyślnie używa płytkiego porównania (shallow equality). Gdy komponent subskrybuje store za pomocą useCartStore(), otrzymuje cały obiekt stanu i re-renderuje się przy każdej zmianie. Wybieranie konkretnych fragmentów zapobiega niepotrzebnym re-renderom:

Component.tsxtypescript
// Źle: re-renderuje się przy każdej zmianie store
const { items } = useCartStore()

// Dobrze: re-renderuje się tylko gdy items się zmienią
const items = useCartStore((state) => state.items)

// Wiele wartości: użyj shallow comparison
import { shallow } from 'zustand/shallow'

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

Komparator shallow z zustand/shallow wykonuje porównanie referencji na właściwościach obiektu, a nie na samym obiekcie.

Zustand vs Context API: Kiedy wybrać które rozwiązanie

To pytanie porównawcze pojawia się na niemal każdej rozmowie kwalifikacyjnej dotyczącej zarządzania stanem. Odpowiedź zależy od częstotliwości aktualizacji i złożoności stanu.

KryteriumContext APIZustand
Rozmiar bundle0 KB (wbudowane)1.2 KB gzipped
Kontrola re-renderówRęczna z memo/useMemoWbudowane selektory
DevToolsTylko React DevToolsKompatybilne z Redux DevTools
Server componentsPełne wsparcieWymaga granicy klienta
Stan asynchronicznyWymaga wrapperaNatywne wsparcie

Context API sprawdza się dobrze dla wartości zmieniających się rzadko, jak motyw czy locale. Zustand przewyższa w sytuacjach, gdy aktualizacje stanu są częste, na przykład inputy formularzy, dane real-time czy koszyki zakupowe.

Rozważania o Server Components

Store Zustand wymagają dyrektywy "use client". Dla aplikacji intensywnie wykorzystujących React Server Components oznacza to, że stan Zustand żyje tylko w komponentach klienckich. Hybrydowe architektury często przekazują dane pobrane z serwera jako props do komponentów klienckich, które następnie synchronizują się z Zustand.

Wzorce middleware w Zustand

Pytania o middleware testują, czy kandydat rozumie kompozycję ponad konfiguracją. Middleware Zustand opakowuje funkcję tworzącą store, dodając zachowanie bez modyfikowania podstawowego API.

Jak działa 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 })
    }
  )
)

Opcja partialize wyklucza funkcje i stan pochodny z persystencji. Bez niej serializacja zawodzi lub storage puchnie z niepotrzebnymi danymi.

Łączenie wielu middleware

Middleware komponuje się od wewnątrz na zewnątrz. Najbardziej wewnętrzne middleware wykonuje się jako pierwsze:

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) => ({
          // stan i akcje
        }))
      ),
      { name: 'app-storage' }
    ),
    { name: 'AppStore' }
  )
)

Middleware immer umożliwia aktualizacje w stylu mutowalnym, które produkują niemutowalny stan. Middleware subscribeWithSelector pozwala na subskrybowanie fragmentów stanu poza komponentami React.

Gotowy na rozmowy o React / Next.js?

Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.

Wzorce integracji TypeScript

Zustand v5 znacząco poprawił wnioskowanie TypeScript. Wzorzec podwójnego wywołania funkcji create<Type>()() zapewnia pełne wnioskowanie typów dla łańcuchów middleware.

Typowanie akcji odnoszących się do innego stanu

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
    }
  }
}))

Stan pochodny jako funkcje zamiast getterów unika przestarzałych wartości. Każde wywołanie filteredTodos() odczytuje aktualny stan.

Dzielenie stores dla dużych aplikacji

Częste pytanie rekrutacyjne dotyczy strukturyzacji Zustand pod kątem skalowalności. Wzorzec slice dzieli store na moduły domenowe:

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,  // Połączony typ 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)
}))

Slice mogą odnosić się do siebie nawzajem poprzez połączony typ store przekazany do StateCreator.

Popularne pytania rekrutacyjne z wzorcowymi odpowiedziami

Kiedy Zustand byłby złym wyborem?

Zustand dodaje złożoność bez korzyści w tych scenariuszach:

  • Statyczne dane konfiguracyjne, które nigdy nie zmieniają się w runtime
  • Stan formularza zarządzany przez React Hook Form lub podobne biblioteki
  • Stan serwera lepiej obsługiwany przez TanStack Query lub SWR
  • Proste przekazywanie props parent-child z 2-3 poziomami zagnieżdżenia

Jak testować komponenty używające Zustand?

Testowanie wymaga resetowania stanu store między testami i opcjonalnie mockowania całego store:

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

beforeEach(() => {
  // Reset store do stanu początkowego
  useCartStore.setState({ items: [], totalItems: 0 })
})

test('addItem zwiększa liczbę w koszyku', () => {
  const { result } = renderHook(() => useCartStore())
  
  act(() => {
    result.current.addItem({ id: '1', name: 'Product', price: 10 })
  })
  
  expect(result.current.items).toHaveLength(1)
})

Dla testów komponentów, które nie powinny wchodzić w interakcję z prawdziwymi stores, mockuj na poziomie modułu:

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

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

Jak Zustand obsługuje operacje asynchroniczne?

W przeciwieństwie do Redux, Zustand nie wymaga middleware dla async. Akcje mogą być bezpośrednio funkcjami 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 })
    }
  }
}))

Dla złożonych przepływów asynchronicznych zaleca się trzymanie stanów loading i error w kolokacji z danymi, które opisują.

Zmiany w Zustand v5, które mogą pojawić się na rozmowach

Zustand v5 wprowadził przełomowe zmiany, których rekruterzy używają do oceny, czy kandydaci są na bieżąco:

  • Brak domyślnych eksportów: Importuj { create } zamiast create
  • Surowszy TypeScript: Wzorzec create<T>()() zastępuje create<T>()
  • Synchroniczna hydratacja persist: Zmieniono timing callbacku onRehydrateStorage
  • Usunięto wsparcie CJS: Pakiet tylko ESM

Migracja z v4 do v5 wymaga aktualizacji instrukcji importu i dostosowania kodu, który polegał na starym timingu hydratacji.

Kluczowe wnioski dla rozmów o Zustand

  • Store Zustand żyją poza drzewem React, umożliwiając synchroniczny dostęp i zapobiegając zagnieżdżaniu providerów
  • Selektory z shallow comparison optymalizują re-rendery dla produkcyjnych aplikacji
  • Middleware persist wymaga partialize do wykluczenia funkcji z storage
  • Wzorzec slice skaluje Zustand do dużych aplikacji bez poświęcania bezpieczeństwa typów
  • Akcje async działają bezpośrednio bez middleware, w przeciwieństwie do wzorców Redux
  • Testowanie wymaga jawnego resetu stanu między przypadkami testowymi
  • Server Components wymuszają granice klienta dla Zustand, wpływając na decyzje architektoniczne

Zacznij ćwiczyć!

Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.

Wyzwanie dnia

Znajdziesz błąd w React / Next.js?

Prawdziwy fragment kodu, ukryty błąd, jedna próba dziennie. Bez konta, żeby spróbować.

Anthony Fillion-Maillet

Autor:

Anthony Fillion-Maillet

Założyciel SharpSkill

Programista fullstack od ponad 10 lat. Prowadzi SharpSkill i odpowiada za wszystko, co się tu ukazuje.

Zaktualizowano 25 sierpnia 2026

Udostępnij

Powiązane artykuły