คำถามสัมภาษณ์ Zustand 2026: การจัดการ State ใน React และแนวปฏิบัติที่ดีที่สุด
คำถามและคำตอบสัมภาษณ์ Zustand สำหรับปี 2026 เรียนรู้ pattern การจัดการ state, middleware, TypeScript และการเปรียบเทียบ Zustand กับ Redux และ Context

Zustand ได้กลายเป็นไลบรารีจัดการ state ที่นิยมมากที่สุดสำหรับแอปพลิเคชัน React ที่ต้องการมากกว่า Context API แต่ไม่ซับซ้อนเท่า Redux ด้วย API ที่เรียบง่ายและการออกแบบที่เน้น TypeScript เป็นหลัก คำถามเกี่ยวกับ Zustand จึงปรากฏในการสัมภาษณ์ frontend อยู่เป็นประจำ โดยเฉพาะสำหรับตำแหน่ง React ระดับกลางและอาวุโส
คำถามสัมภาษณ์ Zustand ทดสอบสามด้าน: ความเข้าใจเกี่ยวกับ pattern ของ store เทียบกับ state ในตัวของ React ความรู้เกี่ยวกับเวลาที่ Zustand ทำงานได้ดีกว่าทางเลือกอื่น และความสามารถในการจัดโครงสร้าง store เพื่อการดูแลรักษา
แนวคิดหลักของ Zustand ที่ผู้สมัครทุกคนควรรู้
Zustand v5 ที่เปิดตัวปลายปี 2024 ได้เปลี่ยนแปลงหลายอย่างจาก v4 ฟังก์ชัน create ไม่จำเป็นต้องเรียก useStore แยกต่างหากอีกต่อไป และ middleware persist ใช้โมเดล hydration แบบ synchronous เป็นค่าเริ่มต้น ผู้สัมภาษณ์คาดหวังให้ผู้สมัครรู้จัก API ปัจจุบัน
อะไรทำให้ Zustand แตกต่างจาก useState และ useReducer?
Zustand store อยู่นอก component tree ของ React ความแตกต่างด้านสถาปัตยกรรมนี้มีผลลัพธ์เชิงปฏิบัติสามประการ:
- State คงอยู่แม้ component จะ mount และ unmount โดยไม่ต้องมี Context provider
- การอัปเดตไม่ทำให้ component พ่อแม่ re-render เฉพาะ component ที่ subscribe เท่านั้นที่ re-render
- State สามารถเข้าถึงแบบ synchronous นอก React ซึ่งมีประโยชน์สำหรับ event handler และ 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)
}))ฟังก์ชัน get ให้การเข้าถึง state ปัจจุบันแบบ synchronous หลีกเลี่ยงปัญหา stale closure ที่พบบ่อยกับ useState
Zustand จัดการ re-render อย่างไร?
Zustand ใช้ shallow equality เป็นค่าเริ่มต้น เมื่อ component subscribe store ด้วย useCartStore() จะได้รับ object state ทั้งหมดและ re-render เมื่อมีการเปลี่ยนแปลงใดๆ การเลือก slice เฉพาะจะป้องกันการ render ที่ไม่จำเป็น:
// ไม่ดี: re-render เมื่อมีการเปลี่ยนแปลง store ใดๆ
const { items } = useCartStore()
// ดี: re-render เฉพาะเมื่อ items เปลี่ยน
const items = useCartStore((state) => state.items)
// หลายค่า: ใช้ shallow comparison
import { shallow } from 'zustand/shallow'
const { items, totalPrice } = useCartStore(
(state) => ({ items: state.items, totalPrice: state.totalPrice() }),
shallow
)Comparator shallow จาก zustand/shallow ทำ reference equality บน property ของ object แทนที่จะเป็น object นั้นเอง
Zustand vs Context API: เมื่อไหร่ควรเลือกแต่ละอัน
คำถามเปรียบเทียบนี้ปรากฏในเกือบทุกการสัมภาษณ์ที่เกี่ยวกับการจัดการ state คำตอบขึ้นอยู่กับความถี่ในการอัปเดตและความซับซ้อนของ state
| เกณฑ์ | Context API | Zustand |
|---|---|---|
| Bundle size | 0 KB (มีอยู่ในตัว) | 1.2 KB gzipped |
| การควบคุม re-render | ทำเองด้วย memo/useMemo | Selector ในตัว |
| DevTools | เฉพาะ React DevTools | รองรับ Redux DevTools |
| Server components | รองรับเต็มที่ | ต้องการ client boundary |
| Async state | ต้องมี wrapper | รองรับโดยตรง |
Context API ทำงานได้ดีสำหรับค่าที่เปลี่ยนแปลงไม่บ่อย เช่น theme หรือ locale Zustand เหนือกว่าเมื่อการอัปเดต state เกิดขึ้นบ่อย เช่น input ของ form ข้อมูล real-time หรือตะกร้าสินค้า เอกสาร React เกี่ยวกับการจัดการ state แนะนำให้ยก state ขึ้นและใช้ Context สำหรับปัญหา "prop drilling" ในขณะที่ external store เหมาะกับ pattern การอัปเดตที่ซับซ้อน
Zustand store ต้องการ directive "use client" สำหรับแอปพลิเคชันที่ใช้ React Server Components อย่างกว้างขวาง หมายความว่า state ของ Zustand มีอยู่ในเฉพาะ client component สถาปัตยกรรมแบบ hybrid มักส่งข้อมูลที่ fetch จาก server เป็น props ไปยัง client component แล้ว sync กับ Zustand
รูปแบบ Middleware ใน Zustand
คำถามเกี่ยวกับ middleware ทดสอบว่าผู้สมัครเข้าใจ composition over configuration หรือไม่ Zustand middleware ครอบฟังก์ชัน store creator เพิ่ม behavior โดยไม่เปลี่ยน core 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 ไม่รวมฟังก์ชันและ derived state จากการ persist หากไม่มีตัวเลือกนี้ serialization จะล้มเหลวหรือ storage จะบวมด้วยข้อมูลที่ไม่จำเป็น เอกสาร Zustand เกี่ยวกับ persist ครอบคลุมกลยุทธ์การ migrate สำหรับการเปลี่ยนแปลง schema
การรวม middleware หลายตัว
Middleware ถูก compose จากในสู่นอก 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) => ({
// state และ actions
}))
),
{ name: 'app-storage' }
),
{ name: 'AppStore' }
)
)Middleware immer ช่วยให้อัปเดตแบบ mutable ที่สร้าง state แบบ immutable Middleware subscribeWithSelector ช่วยให้ subscribe slice ของ state นอก component React
พร้อมที่จะพิชิตการสัมภาษณ์ React / Next.js แล้วหรือยังครับ?
ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ
รูปแบบการรวม TypeScript
Zustand v5 ปรับปรุง type inference อย่างมาก รูปแบบการเรียกฟังก์ชันคู่ create<Type>()() ให้ type inference เต็มรูปแบบสำหรับ middleware chain
การกำหนด type สำหรับ action ที่อ้างอิง state อื่น
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 เป็นฟังก์ชันแทนที่จะเป็น getter หลีกเลี่ยงค่าที่ stale ทุกการเรียก filteredTodos() อ่าน state ปัจจุบัน
การแยก store สำหรับแอปพลิเคชันขนาดใหญ่
คำถามสัมภาษณ์ที่พบบ่อยถามว่าจะจัดโครงสร้าง 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)
}))Slice สามารถอ้างอิงซึ่งกันและกันผ่านประเภท store รวมที่ส่งไปยัง StateCreator
คำถามสัมภาษณ์ทั่วไปพร้อมคำตอบตัวอย่าง
เมื่อไหร่ที่ Zustand ไม่ใช่ตัวเลือกที่ดี?
Zustand เพิ่มความซับซ้อนโดยไม่มีประโยชน์ในสถานการณ์เหล่านี้:
- ข้อมูล configuration แบบ static ที่ไม่เคยเปลี่ยนแปลงขณะ runtime
- State ของ form ที่จัดการโดย React Hook Form หรือไลบรารีที่คล้ายกัน
- Server state ที่ TanStack Query หรือ SWR จัดการได้ดีกว่า
- การส่ง prop แบบ parent-child ง่ายๆ ที่มี 2-3 ระดับของการซ้อนกัน
ส่วนเปรียบเทียบใน repository GitHub ของ Zustand ให้มุมมองของ maintainer เกี่ยวกับทางเลือกอื่น
จะทดสอบ component ที่ใช้ Zustand อย่างไร?
การทดสอบต้องรีเซ็ต state ของ store ระหว่าง test และเลือกที่จะ mock store ทั้งหมด:
import { act, renderHook } from '@testing-library/react'
import { useCartStore } from './store'
beforeEach(() => {
// รีเซ็ต store กลับเป็น state เริ่มต้น
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)
})สำหรับ component test ที่ไม่ควรโต้ตอบกับ store จริง ให้ mock ที่ระดับ module:
import { vi } from 'vitest'
vi.mock('./store', () => ({
useCartStore: vi.fn(() => ({
items: [{ id: '1', name: 'Mock Product', price: 25 }],
addItem: vi.fn()
}))
}))Zustand จัดการการดำเนินการ async อย่างไร?
ต่างจาก Redux Zustand ไม่ต้องการ middleware สำหรับ async Action สามารถเป็นฟังก์ชัน 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 })
}
}
}))สำหรับ async flow ที่ซับซ้อน คู่มือ async pattern ของ Zustand แนะนำให้เก็บ state ของ loading และ error ไว้ร่วมกับข้อมูลที่มันอธิบาย
การเปลี่ยนแปลง Zustand v5 ที่อาจปรากฏในการสัมภาษณ์
Zustand v5 มีการเปลี่ยนแปลงที่ทำให้เข้ากันไม่ได้ซึ่งผู้สัมภาษณ์ใช้เพื่อวัดว่าผู้สมัครตามทันหรือไม่:
- ไม่มี default export อีกต่อไป: Import
{ create }แทนcreate - TypeScript เข้มงวดขึ้น: รูปแบบ
create<T>()()แทนที่create<T>() - Hydration ของ persist แบบ sync: เวลาของ callback
onRehydrateStorageเปลี่ยนไป - ยกเลิกการรองรับ CJS: Package เป็น ESM เท่านั้น
การ migrate จาก v4 ไป v5 ต้องอัปเดตคำสั่ง import และปรับ code ที่พึ่งพา timing hydration เดิม
ประเด็นสำคัญสำหรับการสัมภาษณ์ Zustand
- Store ของ Zustand อยู่นอก React tree ทำให้เข้าถึงแบบ synchronous ได้และป้องกัน provider ซ้อนกัน
- Selector พร้อม shallow comparison เพิ่มประสิทธิภาพ re-render สำหรับแอปพลิเคชัน production
- Middleware persist ต้องการ
partializeเพื่อไม่รวมฟังก์ชันจาก storage - รูปแบบ slice ขยาย Zustand ไปยังแอปพลิเคชันขนาดใหญ่โดยไม่เสียสละ type safety
- Action แบบ async ทำงานโดยตรงโดยไม่ต้องมี middleware ต่างจาก pattern ของ Redux
- การทดสอบต้องรีเซ็ต state อย่างชัดเจนระหว่าง test case
- Server Components บังคับให้ Zustand อยู่ใน client boundary ซึ่งมีผลต่อการตัดสินใจด้านสถาปัตยกรรม
เริ่มฝึกซ้อมเลย!
ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ
คุณหาบั๊กใน React / Next.js เจอไหม
โค้ดจริงหนึ่งชิ้น บั๊กที่ซ่อนอยู่หนึ่งจุด วันละหนึ่งครั้ง ลองได้โดยไม่ต้องมีบัญชี

เขียนโดย
Anthony Fillion-Mailletผู้ก่อตั้ง SharpSkill
เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่
อัปเดตเมื่อ 25 สิงหาคม 2569
แชร์
บทความที่เกี่ยวข้อง

Next.js 16 Middleware ปี 2026: Edge Runtime, การยืนยันตัวตน และคำถามสัมภาษณ์งาน
คู่มือฉบับสมบูรณ์เกี่ยวกับ Next.js 16 Middleware ในปี 2026 เรียนรู้การทำงานของ Edge Runtime การใช้งานระบบยืนยันตัวตน path matching และเตรียมตัวสำหรับคำถามสัมภาษณ์ frontend

React 19 Suspense และ Concurrent Rendering: Streaming SSR พร้อมคำถามสัมภาษณ์งาน 2026
คู่มือฉบับสมบูรณ์สำหรับ React 19 Suspense, concurrent rendering และ streaming SSR พร้อมตัวอย่างโค้ดจริงและคำถามสัมภาษณ์ที่พบบ่อยในปี 2026

การทดสอบ React ในปี 2026: Vitest, React Testing Library และ Best Practices
เชี่ยวชาญการทดสอบ React ด้วย Vitest และ React Testing Library เรียนรู้รูปแบบการทดสอบ component การจัดการ async กลยุทธ์ mocking และ best practices สำหรับการสัมภาษณ์ 2026