ทำไม Zustand ถึงกลายเป็นตัวเลือกอันดับ 1 สำหรับ State Management ใน React Native
ถ้าคุณเป็นนักพัฒนา React Native ในปี 2026 คุณน่าจะเคยได้ยินชื่อ Zustand มาบ้างแล้ว ไลบรารีตัวเล็กๆ ที่ชื่อแปลว่า "state" ในภาษาเยอรมัน กำลังเข้ามาแทนที่ Redux ในใจนักพัฒนามากขึ้นเรื่อยๆ ด้วยขนาดแค่ ~1KB (minified + gzipped) API ที่เรียบง่าย และ performance ที่ยอดเยี่ยม
พูดตรงๆ เลย — สำหรับแอป React Native ขนาด bundle สำคัญมากจริงๆ มันส่งผลโดยตรงต่อเวลาดาวน์โหลดและ startup performance ซึ่ง Zustand ชนะขาดในจุดนี้เมื่อเทียบกับ Redux ที่ขนาดรวม middleware พุ่งไปถึง ~20KB
ในบทความนี้ เราจะพาคุณไปรู้จัก Zustand ตั้งแต่พื้นฐานจนถึงเทคนิคขั้นสูง พร้อมโค้ดตัวอย่างที่ใช้งานได้จริงกับ React Native และ Expo
Zustand คืออะไร? ทำความรู้จักแบบเร็วๆ
Zustand เป็นไลบรารี state management ที่สร้างโดยทีม pmndrs (ทีมเดียวกับ Jotai และ React Spring) ตอนนี้อยู่เวอร์ชัน 5.0.10 (มกราคม 2026) รองรับ React 18-19 และ TypeScript 5+
จุดเด่นหลักๆ ที่ทำให้ Zustand โดดเด่นกว่าตัวเลือกอื่น:
- ไม่ต้องใช้ Provider: ไม่ต้อง wrap คอมโพเนนต์ด้วย Provider เหมือน Redux หรือ Context API ทำให้ component tree สะอาดขึ้นเยอะ
- Hooks-based API: เรียกใช้ state ผ่าน hook ได้เลย รู้สึกเป็นธรรมชาติมากกับ React
- Selective Re-rendering: คอมโพเนนต์จะ re-render เฉพาะเมื่อ state ที่ subscribe ไว้เปลี่ยนแปลงเท่านั้น
- TypeScript first-class: รองรับ TypeScript ได้ดีมากตั้งแต่แรก
- Middleware ในตัว: มี persist, devtools, immer พร้อมใช้งานเลย
ติดตั้ง Zustand ใน React Native / Expo
สำหรับโปรเจกต์ Expo
npx expo install zustand
สำหรับ React Native CLI
# ใช้ npm
npm install zustand
# ใช้ yarn
yarn add zustand
# ใช้ pnpm
pnpm add zustand
แค่นี้เลย จริงๆ นะ ไม่ต้องติดตั้ง middleware หรือ dependency เพิ่มอะไรทั้งนั้น Zustand พร้อมใช้งานได้ทันที
สร้าง Store แรกของคุณ: พื้นฐานที่ต้องรู้
หัวใจของ Zustand คือ store ซึ่งสร้างด้วยฟังก์ชัน create แล้วได้ hook กลับมาใช้งานเลย มาดูตัวอย่างแรกกัน:
// stores/useCounterStore.ts
import { create } from 'zustand';
interface CounterState {
count: number;
increment: () => void;
decrement: () => void;
reset: () => void;
}
export const useCounterStore = create<CounterState>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}));
สังเกตไหมว่า state กับ actions อยู่ในที่เดียวกัน? ไม่ต้องแยก action types, reducers, dispatchers เหมือน Redux ทุกอย่างเรียบง่ายและอ่านรู้เรื่องได้ทันที ตรงนี้แหละที่ชอบมาก
นำไปใช้ในคอมโพเนนต์
// components/Counter.tsx
import React from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { useCounterStore } from '../stores/useCounterStore';
export function Counter() {
const count = useCounterStore((state) => state.count);
const increment = useCounterStore((state) => state.increment);
const decrement = useCounterStore((state) => state.decrement);
return (
<View style={styles.container}>
<Text style={styles.count}>{count}</Text>
<View style={styles.buttons}>
<TouchableOpacity onPress={decrement} style={styles.button}>
<Text style={styles.buttonText}>-</Text>
</TouchableOpacity>
<TouchableOpacity onPress={increment} style={styles.button}>
<Text style={styles.buttonText}>+</Text>
</TouchableOpacity>
</View>
</View>
);
}
ไม่ต้อง wrap ด้วย Provider ใดๆ เลย เรียกใช้ hook ได้ทุกที่ในแอป นี่คือข้อดีที่ทำให้ชีวิตนักพัฒนาง่ายขึ้นเยอะมาก
ตัวอย่างจริง: สร้างระบบ Todo App ด้วย Zustand
ทีนี้มาดูตัวอย่างที่ใกล้เคียงกับงานจริงมากขึ้นกันบ้าง — ระบบ Todo ที่มีการเพิ่ม ลบ และสลับสถานะ:
// stores/useTodoStore.ts
import { create } from 'zustand';
interface Todo {
id: string;
text: string;
completed: boolean;
createdAt: Date;
}
interface TodoState {
todos: Todo[];
addTodo: (text: string) => void;
removeTodo: (id: string) => void;
toggleTodo: (id: string) => void;
clearCompleted: () => void;
}
export const useTodoStore = create<TodoState>((set) => ({
todos: [],
addTodo: (text) =>
set((state) => ({
todos: [
...state.todos,
{
id: Date.now().toString(),
text,
completed: false,
createdAt: new Date(),
},
],
})),
removeTodo: (id) =>
set((state) => ({
todos: state.todos.filter((todo) => todo.id !== id),
})),
toggleTodo: (id) =>
set((state) => ({
todos: state.todos.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
),
})),
clearCompleted: () =>
set((state) => ({
todos: state.todos.filter((todo) => !todo.completed),
})),
}));
state ถูกอัปเดตแบบ immutable เสมอ (สร้าง object ใหม่แทนที่จะแก้ไขของเดิม) Zustand จัดการเรื่อง merge state ให้อัตโนมัติ ไม่ต้องกังวลเรื่องนี้
จัดการ Async Actions: ดึงข้อมูลจาก API
ตรงนี้เป็นจุดที่ Zustand เจ๋งมากจริงๆ — รองรับ async actions ได้เลยโดยไม่ต้องใช้ middleware พิเศษเหมือน Redux Thunk หรือ Redux Saga แค่เขียนฟังก์ชัน async แล้วเรียก set เมื่อพร้อม:
// stores/useUserStore.ts
import { create } from 'zustand';
interface User {
id: number;
name: string;
email: string;
avatar: string;
}
interface UserState {
users: User[];
isLoading: boolean;
error: string | null;
fetchUsers: () => Promise<void>;
refreshUsers: () => Promise<void>;
}
export const useUserStore = create<UserState>((set) => ({
users: [],
isLoading: false,
error: null,
fetchUsers: async () => {
set({ isLoading: true, error: null });
try {
const response = await fetch(
'https://jsonplaceholder.typicode.com/users'
);
const users = await response.json();
set({ users, isLoading: false });
} catch (error) {
set({
error: error instanceof Error ? error.message : 'เกิดข้อผิดพลาด',
isLoading: false,
});
}
},
refreshUsers: async () => {
set({ error: null });
try {
const response = await fetch(
'https://jsonplaceholder.typicode.com/users'
);
const users = await response.json();
set({ users });
} catch (error) {
set({
error: error instanceof Error ? error.message : 'เกิดข้อผิดพลาด',
});
}
},
}));
สังเกตว่า refreshUsers ไม่ได้ set isLoading — เพราะบางทีเราอาจต้องการ silent refresh ที่ไม่แสดง loading indicator ให้ผู้ใช้เห็น ยืดหยุ่นปรับได้ตามที่ต้องการเลย
Persist State: เก็บ State ไว้แม้ปิดแอป
นี่เป็นหนึ่งในฟีเจอร์ที่ใช้บ่อยที่สุดใน React Native เลย การ persist state ข้ามเซสชัน ไม่ว่าจะเป็น authentication token, user preferences หรือ shopping cart — Zustand มี persist middleware ในตัวที่ใช้งานง่ายมากๆ
วิธีที่ 1: ใช้ AsyncStorage (เหมาะสำหรับข้อมูลที่ไม่ critical)
# ติดตั้ง AsyncStorage
npx expo install @react-native-async-storage/async-storage
// stores/useSettingsStore.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
interface SettingsState {
theme: 'light' | 'dark';
language: string;
notificationsEnabled: boolean;
setTheme: (theme: 'light' | 'dark') => void;
setLanguage: (language: string) => void;
toggleNotifications: () => void;
}
export const useSettingsStore = create<SettingsState>()(
persist(
(set) => ({
theme: 'light',
language: 'th',
notificationsEnabled: true,
setTheme: (theme) => set({ theme }),
setLanguage: (language) => set({ language }),
toggleNotifications: () =>
set((state) => ({
notificationsEnabled: !state.notificationsEnabled,
})),
}),
{
name: 'app-settings',
storage: createJSONStorage(() => AsyncStorage),
}
)
);
แค่นี้เอง เมื่อผู้ใช้เปลี่ยน theme หรือ language แล้วปิดแอป ค่าเหล่านี้จะถูกเก็บไว้และโหลดกลับมาอัตโนมัติเมื่อเปิดแอปอีกครั้ง
วิธีที่ 2: ใช้ MMKV (แนะนำสำหรับ production — เร็วกว่า AsyncStorage ~30 เท่า)
สำหรับแอป production ที่ต้องการประสิทธิภาพสูงสุด react-native-mmkv เป็นตัวเลือกที่ดีกว่ามาก MMKV เป็น key-value storage ที่เขียนด้วย C++ พัฒนาโดย WeChat ทำงานแบบ synchronous บน native layer จึงไม่ block JS thread
พูดง่ายๆ คือมันเร็วจนแทบจะรู้สึกเหมือนอ่าน-เขียนจาก memory เลย
# ติดตั้ง MMKV (V4 — Nitro Module)
npm install react-native-mmkv react-native-nitro-modules
# สำหรับ Expo
npx expo install react-native-mmkv react-native-nitro-modules
npx expo prebuild
// lib/mmkvStorage.ts
import { MMKV } from 'react-native-mmkv';
import { StateStorage } from 'zustand/middleware';
const mmkv = new MMKV();
export const mmkvStorage: StateStorage = {
setItem: (name, value) => {
mmkv.set(name, value);
},
getItem: (name) => {
return mmkv.getString(name) ?? null;
},
removeItem: (name) => {
mmkv.delete(name);
},
};
// stores/useAuthStore.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import { mmkvStorage } from '../lib/mmkvStorage';
interface AuthState {
token: string | null;
refreshToken: string | null;
isAuthenticated: boolean;
login: (token: string, refreshToken: string) => void;
logout: () => void;
updateToken: (token: string) => void;
}
export const useAuthStore = create<AuthState>()(
persist(
(set) => ({
token: null,
refreshToken: null,
isAuthenticated: false,
login: (token, refreshToken) =>
set({ token, refreshToken, isAuthenticated: true }),
logout: () =>
set({ token: null, refreshToken: null, isAuthenticated: false }),
updateToken: (token) => set({ token }),
}),
{
name: 'auth-storage',
storage: createJSONStorage(() => mmkvStorage),
partialize: (state) => ({
token: state.token,
refreshToken: state.refreshToken,
isAuthenticated: state.isAuthenticated,
}),
}
)
);
สังเกตการใช้ partialize ตรงนี้ — มันช่วยให้เราเลือกเก็บเฉพาะ state ที่ต้องการ persist ได้ ไม่ต้องเก็บ functions ลงไปด้วย เป็นเทคนิคเล็กๆ ที่มีประโยชน์มาก
MMKV แบบเข้ารหัส: สำหรับข้อมูลที่ Sensitive
ถ้าต้องการความปลอดภัยสูงขึ้น เช่น เก็บ auth token หรือข้อมูลส่วนตัว ก็สามารถใช้ MMKV แบบเข้ารหัสได้:
// lib/secureStorage.ts
import { MMKV } from 'react-native-mmkv';
import { StateStorage } from 'zustand/middleware';
const secureMmkv = new MMKV({
id: 'secure-storage',
encryptionKey: 'your-encryption-key-here',
});
export const secureStorage: StateStorage = {
setItem: (name, value) => {
secureMmkv.set(name, value);
},
getItem: (name) => {
return secureMmkv.getString(name) ?? null;
},
removeItem: (name) => {
secureMmkv.delete(name);
},
};
จัดการ Hydration: ป้องกัน Flash of Initial State
เรื่องนี้เจอบ่อยมาก เวลาใช้ persist middleware แอปจะแสดง initial state ก่อนที่ข้อมูลจาก storage จะโหลดเสร็จ ผลคือผู้ใช้อาจเห็นหน้า login แวบหนึ่งก่อนที่จะเปลี่ยนไปหน้าหลัก (น่ารำคาญมาก)
วิธีแก้คือตรวจสอบสถานะ hydration ก่อน:
// components/HydrationGate.tsx
import React, { useEffect, useState } from 'react';
import { ActivityIndicator, View } from 'react-native';
import { useAuthStore } from '../stores/useAuthStore';
export function HydrationGate({ children }: { children: React.ReactNode }) {
const [hydrated, setHydrated] = useState(false);
useEffect(() => {
const unsub = useAuthStore.persist.onFinishHydration(() => {
setHydrated(true);
});
// กรณี hydration เสร็จแล้วก่อน effect ทำงาน
if (useAuthStore.persist.hasHydrated()) {
setHydrated(true);
}
return unsub;
}, []);
if (!hydrated) {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<ActivityIndicator size="large" />
</View>
);
}
return <>{children}</>;
}
Selectors และ useShallow: ป้องกันการ Re-render ที่ไม่จำเป็น
ตรงนี้สำคัญมาก เป็นเทคนิคที่ต้องรู้ถ้าจะใช้ Zustand ในโปรเจกต์จริง Zustand จะเปรียบเทียบค่าที่ส่งกลับจาก selector ด้วย Object.is ถ้าสร้าง object ใหม่ทุกครั้ง มันจะ re-render ตลอดเลย
วิธีที่ผิด (สร้าง object ใหม่ทุกครั้ง — re-render ไม่หยุด)
// ❌ ไม่ดี — สร้าง object ใหม่ทุกครั้งที่ render
const { count, increment } = useCounterStore((state) => ({
count: state.count,
increment: state.increment,
}));
วิธีที่ถูก: ใช้ useShallow
import { useShallow } from 'zustand/shallow';
// ✅ ใช้ useShallow เพื่อ shallow compare
const { count, increment } = useCounterStore(
useShallow((state) => ({
count: state.count,
increment: state.increment,
}))
);
วิธีที่ดีที่สุด: เลือก state ทีละตัว
// ✅ ดีที่สุด — เลือกทีละตัว
const count = useCounterStore((state) => state.count);
const increment = useCounterStore((state) => state.increment);
จากประสบการณ์ที่ใช้มา การเลือก state ทีละตัวเป็นวิธีที่ปลอดภัยที่สุด แต่ละ selector จะ re-render เฉพาะเมื่อค่าที่เลือกเปลี่ยนเท่านั้น ไม่มีปัญหา reference equality ให้ปวดหัว
จัดโครงสร้าง Store สำหรับแอปขนาดใหญ่
เมื่อแอปเริ่มโตขึ้น การจัดโครงสร้าง store ให้ดีเป็นสิ่งที่ต้องคิดตั้งแต่เนิ่นๆ แนะนำให้แยก store ตาม domain แบบนี้:
src/
├── stores/
│ ├── useAuthStore.ts # Authentication state
│ ├── useUserStore.ts # User profile data
│ ├── useCartStore.ts # Shopping cart
│ ├── useSettingsStore.ts # App settings
│ └── useNotificationStore.ts # Notifications
├── lib/
│ ├── mmkvStorage.ts # MMKV storage adapter
│ └── secureStorage.ts # Encrypted storage
└── components/
└── HydrationGate.tsx # Hydration handler
อย่าพยายามยัดทุกอย่างไว้ใน store เดียว การแยก store ตาม domain ช่วยให้โค้ดอ่านง่าย ดูแลง่าย และลดปัญหา re-rendering ที่ไม่จำเป็นได้อีกด้วย
Middleware ที่มีประโยชน์
DevTools: ดีบัก state ด้วย Redux DevTools
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
export const useAppStore = create<AppState>()(
devtools(
(set) => ({
// state and actions
}),
{ name: 'AppStore' }
)
);
Immer: เขียน state mutation แบบ mutable syntax
ถ้าคุณเคยเขียน Redux Toolkit มาก่อน น่าจะคุ้นเคยกับ Immer อยู่แล้ว ใน Zustand ก็ใช้ได้เหมือนกัน:
import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';
interface CartState {
items: { id: string; name: string; quantity: number }[];
addItem: (item: { id: string; name: string }) => void;
updateQuantity: (id: string, quantity: number) => void;
}
export const useCartStore = create<CartState>()(
immer((set) => ({
items: [],
addItem: (item) =>
set((state) => {
// เขียนแบบ mutable ได้เลย — Immer จัดการ immutability ให้
state.items.push({ ...item, quantity: 1 });
}),
updateQuantity: (id, quantity) =>
set((state) => {
const item = state.items.find((i) => i.id === id);
if (item) {
item.quantity = quantity;
}
}),
}))
);
Immer ช่วยให้เขียน state updates ที่ซับซ้อนได้สะดวกขึ้นมาก โดยเฉพาะกับ nested objects หรือ arrays ที่ถ้าเขียนแบบ immutable เองจะยาวและอ่านยาก
รวม middleware หลายตัวเข้าด้วยกัน
ทีนี้ถ้าจะใช้หลาย middleware พร้อมกัน ก็แค่ wrap ซ้อนกันไป:
import { create } from 'zustand';
import { devtools, persist, createJSONStorage } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
import { mmkvStorage } from '../lib/mmkvStorage';
export const useCartStore = create<CartState>()(
devtools(
persist(
immer((set) => ({
// state and actions
})),
{
name: 'cart-storage',
storage: createJSONStorage(() => mmkvStorage),
}
),
{ name: 'CartStore' }
)
);
Zustand เทียบกับตัวเลือกอื่น: เลือกตัวไหนดี?
คำถามนี้เจอบ่อยมาก แต่ละไลบรารีมีจุดแข็งต่างกัน ลองดูตารางเปรียบเทียบนี้:
| คุณสมบัติ | Zustand | Redux Toolkit | Jotai | Context API |
|---|---|---|---|---|
| ขนาด Bundle | ~1KB | ~20KB | ~4KB | 0 (ในตัว) |
| Boilerplate | น้อยมาก | มาก | น้อย | ปานกลาง |
| ต้องใช้ Provider | ไม่ | ใช่ | ไม่ | ใช่ |
| Learning Curve | ง่าย | สูง | ง่าย | ง่าย |
| Async Actions | ในตัว | ต้องใช้ Thunk | รองรับ | ต้องทำเอง |
| Persist | ในตัว | ต้องใช้ redux-persist | ต้องใช้ plugin | ต้องทำเอง |
| เหมาะกับ | ทุกขนาด | แอปขนาดใหญ่ | state แบบ atomic | แอปเล็ก |
แล้วควรเลือกตัวไหน?
- แอปเล็ก, state ไม่ซับซ้อน: Context API ก็เพียงพอแล้ว ไม่ต้องเพิ่ม dependency
- แอปขนาดเล็ก-กลาง, ต้องการความเรียบง่าย: Zustand เป็นตัวเลือกที่ลงตัวที่สุด
- แอปขนาดใหญ่, ทีมใหญ่, ต้องการ strict pattern: Redux Toolkit ยังคงเป็นทางเลือกที่ดี
- ต้องการ fine-grained reactivity: Jotai เหมาะมาก
- ใช้หลายตัวร่วมกัน: TanStack Query สำหรับ server state + Zustand สำหรับ client state — เป็นชุดคอมโบที่ได้รับความนิยมมากในปี 2026
เทคนิคขั้นสูง: เข้าถึง State นอก React Component
บางครั้งเราจำเป็นต้องเข้าถึง state นอก React component เช่น ใน API interceptor, navigation service หรือ utility function ซึ่ง Zustand ทำได้:
// ดึงค่า state ปัจจุบัน
const token = useAuthStore.getState().token;
// เรียก action
useAuthStore.getState().logout();
// subscribe การเปลี่ยนแปลง
const unsub = useAuthStore.subscribe(
(state) => {
if (!state.isAuthenticated) {
// redirect ไปหน้า login
}
}
);
ลองดูตัวอย่างจริงกับ Axios interceptor — อันนี้ใช้ในโปรเจกต์จริงได้เลย:
// lib/api.ts
import axios from 'axios';
import { useAuthStore } from '../stores/useAuthStore';
const api = axios.create({
baseURL: 'https://api.example.com',
});
api.interceptors.request.use((config) => {
const token = useAuthStore.getState().token;
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
api.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
useAuthStore.getState().logout();
}
return Promise.reject(error);
}
);
export default api;
สรุป
Zustand เป็นไลบรารี state management ที่ตอบโจทย์นักพัฒนา React Native ในปี 2026 ได้อย่างดี ด้วยขนาดแค่ ~1KB, API ที่เรียบง่าย, TypeScript support ที่สมบูรณ์ และ middleware พร้อมใช้งาน ไม่ว่าจะเป็น persist, immer หรือ devtools
สิ่งสำคัญที่ควรจำไปใช้:
- ใช้ selectors แบบแยกตัว หรือ
useShallowเพื่อป้องกัน re-render ที่ไม่จำเป็น - ใช้ MMKV แทน AsyncStorage สำหรับ production — เร็วกว่า ~30 เท่า
- จัดการ hydration ให้ถูกต้องเพื่อป้องกัน flash of initial state
- แยก store ตาม domain ตั้งแต่แรก ไม่ต้องรอจนแอปใหญ่แล้วค่อยมาแก้
- ลองใช้ Zustand คู่กับ TanStack Query สำหรับ server state — เป็นคู่ที่ลงตัวมาก
คำถามที่พบบ่อย (FAQ)
Zustand ดีกว่า Redux จริงหรือ? ควรย้ายจาก Redux มาใช้ Zustand ไหม?
ไม่ใช่ว่า "ดีกว่า" ในทุกกรณี แต่ Zustand เหมาะกว่า สำหรับแอปส่วนใหญ่ในปี 2026 ด้วย boilerplate ที่น้อยกว่า 65% ขนาดเล็กกว่า 20 เท่า และ API ที่เรียนรู้ได้เร็วกว่ามาก สำหรับแอปขนาดใหญ่ที่มีทีมหลายคนและต้องการ strict pattern Redux Toolkit ยังเป็นตัวเลือกที่ดีอยู่ ถ้าจะย้ายจาก Redux ก็แนะนำให้ทำทีละส่วน เริ่มจาก feature ใหม่ๆ ก่อน
Zustand ทำงานกับ Expo ได้ไหม? ต้องตั้งค่าอะไรพิเศษหรือเปล่า?
ได้เลย ไม่ต้องตั้งค่าอะไรเพิ่มเติม แค่ npx expo install zustand ก็พร้อมใช้งาน สำหรับ persist middleware ที่ใช้ AsyncStorage ก็ทำงานได้กับ Expo Go แต่ถ้าจะใช้ MMKV จะต้องใช้ development build เพราะ MMKV ต้องใช้ native module (ตรงนี้ต้องระวังนิดนึง)
ทำไมต้องใช้ MMKV แทน AsyncStorage? ต่างกันยังไง?
MMKV เร็วกว่า AsyncStorage ประมาณ 30-100 เท่า เพราะ MMKV เขียนด้วย C++ ทำงานแบบ synchronous บน native layer ส่วน AsyncStorage ทำงานแบบ asynchronous ผ่าน bridge นอกจากนี้ MMKV ยังรองรับการเข้ารหัสข้อมูลในตัว เหมาะสำหรับเก็บข้อมูล sensitive อย่าง token
Zustand v5 มีอะไรเปลี่ยนจาก v4 บ้าง?
การเปลี่ยนแปลงหลักๆ คือ: ใช้ useSyncExternalStore ภายในเพื่อรองรับ React concurrent rendering ได้ดีขึ้น, error handling ที่ชัดเจนขึ้น (Maximum update depth exceeded จะแสดง error ทันทีแทนที่จะเงียบไป) และ persist middleware ที่แก้ bug สำคัญแล้วในเวอร์ชัน 5.0.10 ข่าวดีคือการอัปเกรดจาก v4 มา v5 ส่วนใหญ่ไม่ต้องแก้โค้ดอะไรเลย
สามารถใช้ Zustand ร่วมกับ TanStack Query ได้ไหม? แบ่งหน้าที่ยังไง?
ใช้ร่วมกันได้ดีมาก และเป็นรูปแบบที่แนะนำสุดๆ ในปี 2026 แบ่งหน้าที่กันง่ายๆ คือ: TanStack Query จัดการ server state (API data, caching, refetching, pagination) ส่วน Zustand จัดการ client state (UI state, user preferences, authentication, shopping cart) วิธีนี้ทำให้โค้ดเป็นระเบียบ ไม่ต้องเก็บ API response ใน global state ที่ไม่จำเป็น