Dark mode
Nuxt.js State Management
ภาพรวม
Nuxt.js มีวิธีการจัดการ State หลายรูปแบบ ทั้งแบบ Built-in และการใช้ Library ภายนอก ซึ่งช่วยให้จัดการข้อมูลที่ใช้ร่วมกันในแอปพลิเคชันได้อย่างมีประสิทธิภาพ
ตัวเลือกการจัดการ State
1. useState (Built-in)
useState
เป็น Composition API ที่มากับ Nuxt ใช้สำหรับจัดการ State แบบ Shared ระหว่างคอมโพเนนต์
typescript
// composables/states.ts
export const useCounter = () => useState<number>("counter", () => 0);
2. Pinia (แนะนำ)
State Management Library มาตรฐานของ Vue Ecosystem
bash
# ติดตั้ง Pinia
npx nuxi module add pinia
3. Library อื่นๆ
- Harlem - สำหรับจัดการ State แบบ Immutable
- XState - ใช้ State Machine ในการจัดการ State
ตารางเปรียบเทียบ
วิธี | การใช้งาน | เหมาะกับ | ข้อดี | ข้อเสีย |
---|---|---|---|---|
useState | ง่าย, ใช้ได้ทันที | State น้อย, ไม่ซับซ้อน | ไม่ต้องติดตั้งเพิ่ม | ไม่มี DevTools, จัดการ State ซับซ้อนยาก |
Pinia | ต้องติดตั้งเพิ่ม | แอปขนาดใหญ่, ต้องการ DevTools | Type Support ดี, มี DevTools | ต้องเรียนรู้เพิ่มเล็กน้อย |
XState | ซับซ้อน | State ซับซ้อน, ต้องการ State Machine | ควบคุมการเปลี่ยน State ได้ละเอียด | เรียนรู้ยาก, Overkill สำหรับแอปเล็ก |
ตัวอย่างการใช้งาน
1. ใช้ useState
vue
<!-- components/Counter.vue -->
<script setup lang="ts">
// เรียกใช้ State เดิมหรือสร้างใหม่ถ้ายังไม่มี
const counter = useState<number>("counter", () => 0);
</script>
<template>
<div>
<p>Counter: {{ counter }}</p>
<button @click="counter++">+</button>
<button @click="counter--">-</button>
</div>
</template>
2. ใช้ Pinia
typescript
// stores/counter.ts
export const useCounterStore = defineStore("counter", {
state: () => ({
count: 0,
name: "Counter",
}),
getters: {
doubleCount: (state) => state.count * 2,
},
actions: {
increment() {
this.count++;
},
async fetchData() {
const { data } = await useFetch("/api/counter");
this.count = data.value?.count || 0;
},
},
});
vue
<!-- components/CounterPinia.vue -->
<script setup lang="ts">
const counterStore = useCounterStore();
</script>
<template>
<div>
<h2>{{ counterStore.name }}</h2>
<p>Count: {{ counterStore.count }}</p>
<p>Double: {{ counterStore.doubleCount }}</p>
<button @click="counterStore.increment()">Increment</button>
<button @click="counterStore.fetchData()">Fetch Data</button>
</div>
</template>
Best Practices
1. การตั้งชื่อ State
typescript
// ดี
const user = useState("user");
// ไม่ดี (อาจเกิดการชนกันของชื่อ)
const data = useState("data");
2. ใช้ Composables จัดการ State
typescript
// composables/useAuth.ts
export const useAuth = () => {
const user = useState("auth:user", () => null);
const isAuthenticated = computed(() => !!user.value);
const login = async (credentials) => {
user.value = await $fetch("/api/login", {
method: "POST",
body: credentials,
});
};
return {
user,
isAuthenticated,
login,
};
};
3. Server-Side State
typescript
// app.vue
const user = useAuthUser();
// ดึงข้อมูลผู้ใช้เมื่อโหลดหน้าเว็บ
await callOnce(async () => {
if (process.server) {
user.value = await $fetch("/api/me");
}
});
การจัดการ State ขั้นสูง
1. Persist State
typescript
// plugins/persistState.client.ts
export default defineNuxtPlugin(() => {
const auth = useAuthUser();
// โหลด State จาก localStorage เมื่อเริ่มต้น
if (process.client) {
const saved = localStorage.getItem("auth");
if (saved) {
auth.value = JSON.parse(saved);
}
// บันทึก State เมื่อมีการเปลี่ยนแปลง
watch(auth, (newValue) => {
localStorage.setItem("auth", JSON.stringify(newValue));
}, { deep: true });
}
});
2. Hydration
typescript
// composables/useHydration.ts
export const useHydration = () => {
// ใช้โหมด SSR
const isHydrated = ref(false);
onMounted(() => {
isHydrated.value = true;
});
return {
isHydrated,
};
};
สรุป
- ใช้
useState
สำหรับ State ง่ายๆ ที่ไม่ซับซ้อน - ใช้ Pinia สำหรับแอปขนาดใหญ่ที่ต้องการความยืดหยุ่น
- ใช้ XState เมื่อต้องการ State Machine
- ควรแยกการจัดการ State ออกเป็น Modules หรือ Composables
- ระวังเรื่อง Hydration เมื่อใช้ SSR
TIP
- ใช้ TypeScript เพื่อความปลอดภัยของประเภทข้อมูล
- ใช้ DevTools เพื่อตรวจสอบ State
- ทดสอบการทำงานของ State Management ให้ครบทุกกรณี