Câu hỏi phỏng vấn Zustand 2026: Quản lý State React và Các phương pháp hay nhất

Câu hỏi và trả lời phỏng vấn Zustand cho năm 2026. Tìm hiểu các pattern quản lý state, middleware, TypeScript, và so sánh Zustand với Redux và Context.

Câu hỏi phỏng vấn Zustand 2026: Quản lý State React và Các phương pháp hay nhất

Zustand đã trở thành thư viện quản lý state được ưa chuộng nhất cho các ứng dụng React cần nhiều hơn Context API nhưng ít phức tạp hơn Redux. Với API tối giản và thiết kế TypeScript-first, các câu hỏi về Zustand hiện xuất hiện thường xuyên trong các buổi phỏng vấn frontend, đặc biệt cho các vị trí React cấp trung và cao cấp.

Nhà tuyển dụng tìm kiếm điều gì

Các câu hỏi phỏng vấn Zustand kiểm tra ba lĩnh vực: hiểu biết về pattern store so với state tích hợp của React, kiến thức về thời điểm Zustand vượt trội hơn các giải pháp thay thế, và khả năng cấu trúc store để dễ bảo trì.

Các khái niệm cốt lõi của Zustand mà mọi ứng viên cần biết

Zustand v5, được phát hành cuối năm 2024, giới thiệu một số breaking changes từ v4. Hàm create không còn yêu cầu gọi useStore riêng biệt, và middleware persist hiện sử dụng model hydration đồng bộ theo mặc định. Nhà tuyển dụng kỳ vọng ứng viên biết API hiện tại.

Điều gì làm Zustand khác biệt với useState và useReducer?

Zustand store tồn tại bên ngoài component tree của React. Sự khác biệt về kiến trúc này có ba hệ quả thực tế:

  1. State tồn tại qua các lần mount và unmount component mà không cần Context provider
  2. Cập nhật không trigger re-render component cha, chỉ các component đã subscribe mới re-render
  3. State có thể được truy cập đồng bộ bên ngoài React, hữu ích cho event handler và logic async
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)
}))

Hàm get cung cấp quyền truy cập đồng bộ vào state hiện tại, tránh vấn đề stale closure thường gặp với useState.

Zustand xử lý re-render như thế nào?

Zustand sử dụng shallow equality theo mặc định. Khi một component subscribe store với useCartStore(), nó nhận toàn bộ object state và re-render khi có bất kỳ thay đổi nào. Chọn các slice cụ thể ngăn chặn render không cần thiết:

Component.tsxtypescript
// Xấu: re-render khi có bất kỳ thay đổi store nào
const { items } = useCartStore()

// Tốt: chỉ re-render khi items thay đổi
const items = useCartStore((state) => state.items)

// Nhiều giá trị: sử dụng shallow comparison
import { shallow } from 'zustand/shallow'

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

Comparator shallow từ zustand/shallow thực hiện reference equality trên các property của object thay vì chính object đó.

Zustand vs Context API: Khi nào chọn mỗi loại

Câu hỏi so sánh này xuất hiện trong hầu hết mọi buổi phỏng vấn về quản lý state. Câu trả lời phụ thuộc vào tần suất cập nhật và độ phức tạp của state.

Tiêu chíContext APIZustand
Bundle size0 KB (tích hợp sẵn)1.2 KB gzipped
Kiểm soát re-renderThủ công với memo/useMemoSelector tích hợp
DevToolsChỉ React DevToolsTương thích Redux DevTools
Server componentsHỗ trợ đầy đủYêu cầu client boundary
Async stateCần wrapperHỗ trợ native

Context API hoạt động tốt cho các giá trị hiếm khi thay đổi như theme hoặc locale. Zustand vượt trội khi cập nhật state xảy ra thường xuyên, như input form, dữ liệu real-time, hoặc giỏ hàng. Tài liệu React về quản lý state khuyến nghị lifting state up và sử dụng Context cho vấn đề "prop drilling", trong khi external store phù hợp cho các pattern cập nhật phức tạp.

Cân nhắc về Server Components

Zustand store yêu cầu directive "use client". Đối với các ứng dụng sử dụng React Server Components rộng rãi, điều này có nghĩa state Zustand chỉ tồn tại trong client component. Kiến trúc hybrid thường truyền dữ liệu fetch từ server dưới dạng props cho client component sau đó sync với Zustand.

Các pattern Middleware trong Zustand

Các câu hỏi về middleware kiểm tra liệu ứng viên có hiểu composition over configuration hay không. Middleware Zustand bao bọc hàm store creator, thêm behavior mà không thay đổi core API.

Middleware persist hoạt động như thế nào?

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

Tùy chọn partialize loại trừ các hàm và derived state khỏi persistence. Không có tùy chọn này, serialization sẽ thất bại hoặc storage phình to với dữ liệu không cần thiết. Tài liệu Zustand về persist đề cập đến các chiến lược migration cho thay đổi schema.

Kết hợp nhiều middleware

Middleware được compose từ trong ra ngoài. Middleware trong cùng được thực thi trước:

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) => ({
          // state và actions
        }))
      ),
      { name: 'app-storage' }
    ),
    { name: 'AppStore' }
  )
)

Middleware immer cho phép cập nhật kiểu mutable tạo ra state immutable. Middleware subscribeWithSelector cho phép subscribe các slice state bên ngoài component React.

Sẵn sàng chinh phục phỏng vấn React / Next.js?

Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.

Các pattern tích hợp TypeScript

Zustand v5 cải thiện type inference đáng kể. Pattern gọi hàm kép create<Type>()() cung cấp type inference đầy đủ cho chuỗi middleware.

Định kiểu action tham chiếu state khác

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

Derived state dưới dạng hàm thay vì getter tránh các giá trị stale. Mỗi lần gọi filteredTodos() đọc state hiện tại.

Chia tách store cho ứng dụng lớn

Một câu hỏi phỏng vấn phổ biến hỏi cách cấu trúc Zustand cho quy mô lớn. Pattern slice chia store thành các module theo domain:

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,  // Kiểu store kết hợp
  [],
  [],
  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)
}))

Các slice có thể tham chiếu lẫn nhau thông qua kiểu store kết hợp được truyền cho StateCreator.

Các câu hỏi phỏng vấn phổ biến với câu trả lời mẫu

Khi nào Zustand là lựa chọn không phù hợp?

Zustand thêm độ phức tạp mà không mang lại lợi ích trong các tình huống sau:

  • Dữ liệu cấu hình tĩnh không bao giờ thay đổi tại runtime
  • State form được quản lý bởi React Hook Form hoặc các thư viện tương tự
  • Server state được xử lý tốt hơn bởi TanStack Query hoặc SWR
  • Truyền prop parent-child đơn giản với 2-3 cấp độ lồng nhau

Phần so sánh trong repository GitHub của Zustand cung cấp quan điểm của maintainer về các lựa chọn thay thế.

Làm thế nào để test component sử dụng Zustand?

Việc test yêu cầu reset state store giữa các test và tùy chọn mock store hoàn toàn:

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

beforeEach(() => {
  // Reset store về state ban đầu
  useCartStore.setState({ items: [], totalItems: 0 })
})

test('addItem increases cart count', () => {
  const { result } = renderHook(() => useCartStore())
  
  act(() => {
    result.current.addItem({ id: '1', name: 'Product', price: 10 })
  })
  
  expect(result.current.items).toHaveLength(1)
})

Đối với component test không nên tương tác với store thực, mock ở cấp module:

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 xử lý các thao tác async như thế nào?

Khác với Redux, Zustand không yêu cầu middleware cho async. Action có thể trực tiếp là hàm 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 })
    }
  }
}))

Đối với các luồng async phức tạp, hướng dẫn pattern async của Zustand khuyến nghị giữ state loading và error cùng vị trí với dữ liệu mà chúng mô tả.

Các thay đổi Zustand v5 có thể xuất hiện trong phỏng vấn

Zustand v5 giới thiệu các breaking changes mà nhà tuyển dụng sử dụng để đánh giá liệu ứng viên có cập nhật kiến thức không:

  • Không còn default export: Import { create } thay vì create
  • TypeScript nghiêm ngặt hơn: Pattern create<T>()() thay thế create<T>()
  • Hydration persist đồng bộ: Thời điểm callback onRehydrateStorage thay đổi
  • Ngừng hỗ trợ CJS: Package chỉ ESM

Việc migrate từ v4 sang v5 yêu cầu cập nhật câu lệnh import và điều chỉnh code phụ thuộc vào timing hydration cũ.

Những điểm chính cho phỏng vấn Zustand

  • Store Zustand tồn tại bên ngoài React tree, cho phép truy cập đồng bộ và ngăn chặn provider lồng nhau
  • Selector với shallow comparison tối ưu hóa re-render cho ứng dụng production
  • Middleware persist yêu cầu partialize để loại trừ hàm khỏi storage
  • Pattern slice mở rộng Zustand cho ứng dụng lớn mà không hy sinh type safety
  • Action async hoạt động trực tiếp không cần middleware, khác với pattern Redux
  • Test yêu cầu reset state rõ ràng giữa các test case
  • Server Components buộc Zustand vào client boundary, ảnh hưởng đến quyết định kiến trúc

Bắt đầu luyện tập!

Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.

Thử thách hôm nay

Bạn có tìm ra lỗi trong React / Next.js không?

Một đoạn mã thật, một lỗi ẩn, mỗi ngày một lượt. Không cần tài khoản để thử.

Anthony Fillion-Maillet

Viết bởi

Anthony Fillion-Maillet

Người sáng lập SharpSkill

Lập trình viên fullstack hơn 10 năm. Anh điều hành SharpSkill và chịu trách nhiệm về mọi nội dung đăng tại đây.

Cập nhật ngày 25 tháng 8, 2026

Chia sẻ

Bài viết liên quan