คำถามสัมภาษณ์ Zustand 2026: การจัดการ State ใน React และแนวปฏิบัติที่ดีที่สุด

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

คำถามสัมภาษณ์ Zustand 2026: การจัดการ State ใน React และแนวปฏิบัติที่ดีที่สุด

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 ความแตกต่างด้านสถาปัตยกรรมนี้มีผลลัพธ์เชิงปฏิบัติสามประการ:

  1. State คงอยู่แม้ component จะ mount และ unmount โดยไม่ต้องมี Context provider
  2. การอัปเดตไม่ทำให้ component พ่อแม่ re-render เฉพาะ component ที่ subscribe เท่านั้นที่ re-render
  3. State สามารถเข้าถึงแบบ synchronous นอก React ซึ่งมีประโยชน์สำหรับ event handler และ 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)
}))

ฟังก์ชัน get ให้การเข้าถึง state ปัจจุบันแบบ synchronous หลีกเลี่ยงปัญหา stale closure ที่พบบ่อยกับ useState

Zustand จัดการ re-render อย่างไร?

Zustand ใช้ shallow equality เป็นค่าเริ่มต้น เมื่อ component subscribe store ด้วย useCartStore() จะได้รับ object state ทั้งหมดและ re-render เมื่อมีการเปลี่ยนแปลงใดๆ การเลือก slice เฉพาะจะป้องกันการ render ที่ไม่จำเป็น:

Component.tsxtypescript
// ไม่ดี: 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 APIZustand
Bundle size0 KB (มีอยู่ในตัว)1.2 KB gzipped
การควบคุม re-renderทำเองด้วย memo/useMemoSelector ในตัว
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 การอัปเดตที่ซับซ้อน

การพิจารณา Server Components

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 ทำงานอย่างไร?

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

ตัวเลือก partialize ไม่รวมฟังก์ชันและ derived state จากการ persist หากไม่มีตัวเลือกนี้ serialization จะล้มเหลวหรือ storage จะบวมด้วยข้อมูลที่ไม่จำเป็น เอกสาร Zustand เกี่ยวกับ persist ครอบคลุมกลยุทธ์การ migrate สำหรับการเปลี่ยนแปลง schema

การรวม middleware หลายตัว

Middleware ถูก compose จากในสู่นอก Middleware ในสุดถูกดำเนินการก่อน:

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 และ 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 อื่น

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 เป็นฟังก์ชันแทนที่จะเป็น getter หลีกเลี่ยงค่าที่ stale ทุกการเรียก filteredTodos() อ่าน state ปัจจุบัน

การแยก store สำหรับแอปพลิเคชันขนาดใหญ่

คำถามสัมภาษณ์ที่พบบ่อยถามว่าจะจัดโครงสร้าง Zustand อย่างไรสำหรับขนาดใหญ่ รูปแบบ slice แบ่ง store เป็นโมดูลตามโดเมน:

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,  // ประเภท 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 สามารถอ้างอิงซึ่งกันและกันผ่านประเภท 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 ทั้งหมด:

store.test.tstypescript
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:

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 จัดการการดำเนินการ async อย่างไร?

ต่างจาก Redux Zustand ไม่ต้องการ middleware สำหรับ async Action สามารถเป็นฟังก์ชัน 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 })
    }
  }
}))

สำหรับ 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

เขียนโดย

Anthony Fillion-Maillet

ผู้ก่อตั้ง SharpSkill

เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่

อัปเดตเมื่อ 25 สิงหาคม 2569

แชร์

บทความที่เกี่ยวข้อง