Zustand — Cart Store
ทำไมต้อง Zustand?
Section titled “ทำไมต้อง Zustand?”Zustand เป็น state management library ที่เล็กและเรียบง่าย ต่างจาก Redux ที่ต้องเขียน boilerplate เยอะ
flowchart TB
subgraph Comparison
Redux[Redux<br/>Boilerplate เยอะ]
Zustand[Zustand<br/>Simple & Light]
Context[Context API<br/>Re-render ทุกครั้ง]
end
classDef good fill:#34d399,stroke:#059669,stroke-width:2px
classDef ok fill:#fbbf24,stroke:#d97706,stroke-width:2px
class Zustand good
class Redux,Context ok
Zustand ใช้งานง่ายมาก — สร้าง store คล้าย ๆ การสร้าง object ธรรมดาเลย
ติดตั้ง Zustand
Section titled “ติดตั้ง Zustand”npm install zustandสร้าง Cart Store
Section titled “สร้าง Cart Store”Step 1: กำหนด Type ของ Product
Section titled “Step 1: กำหนด Type ของ Product”export interface Product { id: string name: string price: number image: string}
export interface CartItem extends Product { quantity: number}Step 2: สร้าง Cart Store
Section titled “Step 2: สร้าง Cart Store”import { create } from "zustand"import { persist } from "zustand/middleware"import { CartItem, Product } from "../types/product"
interface CartStore { items: CartItem[] addItem: (product: Product) => void removeItem: (productId: string) => void updateQuantity: (productId: string, quantity: number) => void clearCart: () => void getTotal: () => number}
export const useCartStore = create<CartStore>()( persist( (set, get) => ({ items: [],
addItem: (product: Product) => { const items = get().items const existingItem = items.find(item => item.id === product.id)
if (existingItem) { set({ items: items.map(item => item.id === product.id ? { ...item, quantity: item.quantity + 1 } : item ) }) } else { set({ items: [...items, { ...product, quantity: 1 }] }) } },
removeItem: (productId: string) => { set({ items: get().items.filter(item => item.id !== productId) }) },
updateQuantity: (productId: string, quantity: number) => { if (quantity <= 0) { get().removeItem(productId) return } set({ items: get().items.map(item => item.id === productId ? { ...item, quantity } : item ) }) },
clearCart: () => set({ items: [] }),
getTotal: () => { const items = get().items return items.reduce((total, item) => total + item.price * item.quantity, 0) } }), { name: "cart-storage" // ชื่อ key ใน localStorage } ))Step 3: ใช้งาน Cart Store ใน Component
Section titled “Step 3: ใช้งาน Cart Store ใน Component”import { useCartStore } from "../stores/cartStore"
export function CartIcon() { const items = useCartStore(state => state.items) const itemCount = items.reduce((sum, item) => sum + item.quantity, 0)
return ( <div className="relative"> <span className="text-2xl">🛒</span> {itemCount > 0 && ( <span className="absolute -top-2 -right-2 bg-red-500 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center"> {itemCount} </span> )} </div> )}import { Product } from "../types/product"import { useCartStore } from "../stores/cartStore"
interface ProductCardProps { product: Product}
export function ProductCard({ product }: ProductCardProps) { const addItem = useCartStore(state => state.addItem)
return ( <div className="border rounded-lg p-4"> <img src={product.image} alt={product.name} className="w-full h-48 object-cover rounded" /> <h3 className="font-semibold mt-2">{product.name}</h3> <p className="text-gray-600">${product.price}</p> <button onClick={() => addItem(product)} className="mt-2 w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700" > Add to Cart </button> </div> )}import { useCartStore } from "../stores/cartStore"
export function CartPage() { const { items, removeItem, updateQuantity, clearCart, getTotal } = useCartStore()
if (items.length === 0) { return <div className="text-center py-8">Your cart is empty</div> }
return ( <div className="max-w-2xl mx-auto"> <h1 className="text-2xl font-bold mb-4">Shopping Cart</h1>
{items.map(item => ( <div key={item.id} className="flex items-center gap-4 border-b py-4"> <img src={item.image} alt={item.name} className="w-20 h-20 object-cover rounded" /> <div className="flex-1"> <h3 className="font-semibold">{item.name}</h3> <p className="text-gray-600">${item.price} x {item.quantity}</p> </div> <div className="flex items-center gap-2"> <button onClick={() => updateQuantity(item.id, item.quantity - 1)} className="px-2 py-1 bg-gray-200 rounded" > - </button> <span>{item.quantity}</span> <button onClick={() => updateQuantity(item.id, item.quantity + 1)} className="px-2 py-1 bg-gray-200 rounded" > + </button> <button onClick={() => removeItem(item.id)} className="ml-4 text-red-600 hover:text-red-700" > 🗑️ </button> </div> </div> ))}
<div className="mt-4 text-right"> <p className="text-xl font-bold">Total: ${getTotal()}</p> <button onClick={clearCart} className="mt-4 px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700" > Clear Cart </button> </div> </div> )}ทำไม Zustand ถึงดี?
Section titled “ทำไม Zustand ถึงดี?”| ฟีเจอร์ | Zustand | Redux | Context |
|---|---|---|---|
| Boilerplate | น้อย | เยอะ | ไม่มี |
| เลือกอ่าน state | ✅ | ✅ | ❌ |
| Middleware (persist) | ✅ | ✅ | ❌ |
| ขนาด | ~1KB | ใหญ่ | Built-in |
| Performance | ดีมาก | ดี | เสี่ยง re-render |
persist middleware ทำให้ cart จำค่าไว้ใน localStorage อัตโนมัติ ไม่ต้องเขียน useEffect เอง
📝 สรุป
Section titled “📝 สรุป”flowchart LR
Product[Product] -->|"addItem"| CartStore[CartStore<br/>Zustand]
CartStore -->|"items, total"| CartUI[Cart UI]
CartUI -->|"updateQuantity"| CartStore
classDef store fill:#f472b6,stroke:#db2777,stroke-width:2px
class CartStore store
create()— สร้าง storepersist()— เก็บใน localStorage อัตโนมัติuseCartStore()— hook สำหรับเข้าถึง state- Selector — เลือกอ่านบางส่วนของ state ได้ เพื่อ performance
🎯 ต่อไป
Section titled “🎯 ต่อไป”03. เชื่อม UI กับ State — นำทุกอย่างมารวมกัน
Quiz: Zustand Cart Store
ข้อ 1 / 40%