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.

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.
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ế:
- State tồn tại qua các lần mount và unmount component mà không cần Context provider
- Cập nhật không trigger re-render component cha, chỉ các component đã subscribe mới re-render
- State có thể được truy cập đồng bộ bên ngoài React, hữu ích cho event handler và logic async
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:
// 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 API | Zustand |
|---|---|---|
| Bundle size | 0 KB (tích hợp sẵn) | 1.2 KB gzipped |
| Kiểm soát re-render | Thủ công với memo/useMemo | Selector tích hợp |
| DevTools | Chỉ React DevTools | Tương thích Redux DevTools |
| Server components | Hỗ trợ đầy đủ | Yêu cầu client boundary |
| Async state | Cần wrapper | Hỗ 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.
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?
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:
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
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:
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 })
})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:
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:
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:
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
onRehydrateStoragethay đổ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.
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ử.

Viết bởi
Anthony Fillion-MailletNgườ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

Next.js 16 Middleware 2026: Edge Runtime, Xác thực và Câu hỏi Phỏng vấn
Hướng dẫn toàn diện về Next.js 16 Middleware trong năm 2026. Tìm hiểu cách Edge Runtime hoạt động, triển khai xác thực, path matching và chuẩn bị cho câu hỏi phỏng vấn frontend.

React 19 Suspense và Concurrent Rendering: Streaming SSR cùng Câu hỏi Phỏng vấn 2026
Hướng dẫn toàn diện về React 19 Suspense, concurrent rendering và streaming SSR. Bao gồm các ví dụ code thực tế và câu hỏi phỏng vấn thường gặp năm 2026.

Testing React trong 2026: Vitest, React Testing Library và Best Practices
Nắm vững testing React với Vitest và React Testing Library. Tìm hiểu các mẫu testing component, xử lý async, chiến lược mocking, và best practices phỏng vấn 2026.