Dark mode
Vue Best Practices
Props Default Values
การกําหนดค่าเริ่มต้นให้กับ props แบบเดิมใช้ computed ซึ่งทําให้โค้ดยาวและซับซ้อน แบบใหม่ใช้ withDefaults ที่ทําให้โค้ดสั้นลงและอ่านง่ายขึ้น
❌ แบบเดิม
ts
// ใช้ computed เพื่อกําหนดค่า default
const props = defineProps<{
orientation?: "horizontal" | "vertical";
}>();
const orientation = computed(() => props.orientation || "vertical");
✅ แบบใหม่
ts
withDefaults(
defineProps<{
orientation?: "horizontal" | "vertical";
}>(),
{
orientation: "vertical",
},
);
Event Handling
การจัดการ event ใน Vue มีการพัฒนาให้ type-safe มากขึ้น แบบเดิมไม่มีการระบุ type ของ event ทําให้อาจเกิดข้อผิดพลาดได้ แบบใหม่ใช้ defineEmits พร้อม type ที่ชัดเจน ทําให้ TypeScript สามารถตรวจจับ error ได้ตั้งแต่ตอน development
❌ แบบเดิม
vue
<template>
<button @click="handleClick">Click me</button>
</template>
<script setup>
const handleClick = (event: MouseEvent) => {
// handle click
}
</script>
Lifecycle Hooks
การจัดการ lifecycle hooks แบบเดิมมักจะแยก setup และ cleanup ออกจากกัน ทําให้ยากต่อการดูแลรักษา แบบใหม่รวม setup และ cleanup ไว้ด้วยกันใน composable function ทําให้โค้ดเป็นระเบียบและป้องกันการลืม cleanup
❌ แบบเดิม
ts
onMounted(() => {
// setup
});
onUnmounted(() => {
// cleanup
});
✅ แบบใหม่
ts
// composable with automatic cleanup
function useSetup() {
onMounted(() => {
const handler = setInterval(() => {
// do something
}, 1000);
// automatic cleanup
onUnmounted(() => {
clearInterval(handler);
});
});
}
Error Handling
การจัดการ error แบบเดิมใช้ try-catch ธรรมดา ซึ่งอาจทําให้ลืมจัดการ error ในบางกรณี แบบใหม่ใช้ useAsyncData หรือ ErrorBoundary component ที่ช่วยจัดการ error ได้เป็นระบบมากขึ้น และมี type safety
❌ แบบเดิม
ts
try {
await someAsyncOperation();
} catch (error) {
console.error(error);
}
✅ แบบใหม่
ts
const { error } = await useAsyncData(async () => {
return await someAsyncOperation()
})
// หรือใช้ error boundary
<template>
<ErrorBoundary @error="handleError">
<AsyncComponent />
</ErrorBoundary>
</template>
Computed Properties
การใช้ computed properties แบบเดิมต้องใช้ .value เมื่อเข้าถึงค่า ref ทําให้โค้ดยาวและอ่านยาก แบบใหม่ใช้ reactive sugar syntax ($ref, $computed) ที่ช่วยลดการใช้ .value ทําให้โค้ดสั้นและอ่านง่ายขึ้น
❌ แบบเดิม
ts
const firstName = ref("");
const lastName = ref("");
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`.trim();
});
✅ แบบใหม่
ts
// ใช้ reactive sugar syntax
const { firstName, lastName } = $ref("");
// ไม่ต้องใช้ .value
const fullName = $computed(() => `${firstName} ${lastName}`.trim());
Composables Organization
การจัดการ state และ logic แบบเดิมเขียนรวมกันในคอมโพเนนต์ แบบใหม่แยกเป็น composable ทําให้นํากลับมาใช้ใหม่ได้และทดสอบง่ายขึ้น
❌ แบบเดิม
ts
const loading = ref(false);
const error = ref(null);
const data = ref(null);
const fetchData = async () => {
loading.value = true;
try {
data.value = await api.getData();
} catch (e) {
error.value = e;
} finally {
loading.value = false;
}
};
✅ แบบใหม่
ts
function useFetch() {
const loading = ref(false);
const error = ref(null);
const data = ref(null);
async function execute() {
loading.value = true;
try {
data.value = await api.getData();
} catch (e) {
error.value = e;
} finally {
loading.value = false;
}
}
return {
loading,
error,
data,
execute,
};
}
// การใช้งาน
const { loading, error, data, execute } = useFetch();
Props Validation
การตรวจสอบ props แบบเดิมใช้ validator function แบบใหม่ใช้ TypeScript type ทําให้ตรวจสอบได้ตั้งแต่ตอน compile
❌ แบบเดิม
ts
const props = defineProps({
size: {
type: String,
validator: (value) => ["small", "medium", "large"].includes(value),
},
});
✅ แบบใหม่
ts
const props = defineProps<{
size: "small" | "medium" | "large";
}>();
Provide/Inject
การใช้ provide/inject แบบเดิมไม่มี type safety แบบใหม่ใช้ Symbol และ TypeScript ทําให้ type-safe
❌ แบบเดิม
ts
// parent
provide("theme", ref("dark"));
// child
const theme = inject("theme");
✅ แบบใหม่
ts
// types.ts
export interface ThemeInjection {
theme: Ref<"light" | "dark">;
toggleTheme: () => void;
}
export const themeKey = Symbol() as InjectionKey<ThemeInjection>;
// parent
provide(themeKey, {
theme: ref("dark"),
toggleTheme: () => theme.value = theme.value === "dark" ? "light" : "dark",
});
// child
const { theme, toggleTheme } = inject(themeKey)!;
✅ แบบใหม่
vue
<template>
<button @click="(e) => emit('click', e)">Click me</button>
</template>
<script setup>
const emit = defineEmits<{
(e: 'click', event: MouseEvent): void
}>()
</script>
Reactive Variables
การใช้ reactive variables แบบเดิมต้องใช้ .value บ่อยๆ แบบใหม่ใช้ $ syntax ทําให้โค้ดสั้นและอ่านง่ายขึ้น
❌ แบบเดิม
ts
// ต้องใช้ .value
const count = ref(0);
const doubled = computed(() => count.value * 2);
✅ แบบใหม่
ts
// ใช้ $ syntax ไม่ต้องใช้ .value
const { count, doubled } = $(useCounter());
function useCounter() {
const count = 0;
const doubled = computed(() => count * 2);
return { count, doubled };
}
Template Refs
การใช้ template refs แบบเดิมต้องใช้ .value เมื่อเข้าถึงค่า ref ทําให้โค้ดยาวและอ่านยาก แบบใหม่ใช้ $ref syntax ที่ช่วยลดการใช้ .value ทําให้โค้ดสั้นและอ่านง่ายขึ้น
❌ แบบเดิม
vue
<template>
<div ref="elementRef"></div>
</template>
<script setup>
const elementRef = ref<HTMLDivElement>()
onMounted(() => {
console.log(elementRef.value)
})
</script>
✅ แบบใหม่
vue
<template>
<div ref="el"></div>
</template>
<script setup>
const el = $ref<HTMLDivElement>()
// ไม่ต้องใช้ .value
console.log(el)
</script>
Watchers
การใช้ watchers แบบเดิมต้องระบุ callback function แยกต่างหาก แบบใหม่ใช้ watchEffect ที่สามารถ track dependencies อัตโนมัติ ทําให้โค้ดสั้นและเข้าใจง่ายขึ้น
❌ แบบเดิม
ts
watch(
() => props.value,
(newValue) => {
// do something
},
);
✅ แบบใหม่
ts
watchEffect(() => {
// automatically tracks props.value
console.log(props.value);
});
v-model
การใช้ v-model แบบเดิมต้องกําหนด props และ emit events แยกกัน แบบใหม่ใช้ defineModel ที่รวมทั้งสองส่วนเข้าด้วยกัน ทําให้โค้ดสั้นและเข้าใจง่ายขึ้น
❌ แบบเดิม
vue
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
>
</template>
<script setup>
defineProps<{
modelValue: string
}>()
defineEmits<{
(e: 'update:modelValue', value: string): void
}>()
</script>
✅ แบบใหม่
vue
<template>
<input v-model="value">
</template>
<script setup>
const value = defineModel<string>()
</script>
Async Components
การโหลด component แบบ async แบบเดิมใช้ defineAsyncComponent เพียงอย่างเดียว แบบใหม่ใช้ร่วมกับ Suspense และมีการจัดการ loading state ที่ดีกว่า
❌ แบบเดิม
ts
// โหลด component แบบ async อย่างเดียว
const AsyncComponent = defineAsyncComponent(() =>
import("./components/AsyncComponent.vue")
);
✅ แบบใหม่
vue
<template>
<Suspense>
<!-- ใช้ Suspense ในการจัดการ async state -->
<AsyncComponent />
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
</template>
<script setup>
import { defineAsyncComponent } from "vue";
// กําหนดค่า options เพิ่มเติมเพื่อจัดการ loading state
const AsyncComponent = defineAsyncComponent({
loader: () => import("./components/AsyncComponent.vue"),
loadingComponent: LoadingSpinner,
delay: 200, // แสดง loading หลังจาก 200ms
timeout: 3000, // timeout หลัง 3 วินาที
});
</script>