Astro กับ Vue: ใช้ Vue 3 Components ใน Astro
#astro13 เม.ย. 2569
Astro กับ Vue: ใช้ Vue 3 Components ใน Astro
Vue 3 เป็นหนึ่งใน JavaScript framework ที่ได้รับความนิยมสูงสุด และ Astro รองรับการใช้งาน Vue components ได้อย่างสมบูรณ์แบบ บทความนี้จะแนะนำวิธีการผสาน Vue 3 เข้ากับ Astro project ตั้งแต่การติดตั้งจนถึงการใช้งานขั้นสูง
ทำไมต้องใช้ Vue กับ Astro?
Vue 3 มีคุณสมบัติที่น่าสนใจหลายอย่าง:
- Composition API: เขียน logic ที่ reusable และ maintainable ได้ง่าย
- TypeScript Support: รองรับ TypeScript อย่างสมบูรณ์
- Reactivity System: ระบบ reactivity ที่ทรงพลังและเข้าใจง่าย
- Ecosystem ขนาดใหญ่: มี library และ plugin มากมาย
การติดตั้ง Vue Integration
npx astro add vue
หรือติดตั้งแบบ manual:
npm install @astrojs/vue vue
แก้ไข astro.config.mjs:
import { defineConfig } from 'astro/config';
import vue from '@astrojs/vue';
export default defineConfig({
integrations: [
vue({
appEntrypoint: '/src/pages/_app', // optional
}),
],
});
สร้าง Vue Component ด้วย Composition API
สร้างไฟล์ src/components/TodoList.vue:
<template>
<div class="todo-app">
<h2>รายการสิ่งที่ต้องทำ</h2>
<div class="input-group">
<input
v-model="newTodo"
@keyup.enter="addTodo"
placeholder="เพิ่มรายการใหม่..."
type="text"
/>
<button @click="addTodo">เพิ่ม</button>
</div>
<ul class="todo-list">
<li
v-for="todo in todos"
:key="todo.id"
:class="{ completed: todo.done }"
>
<input
type="checkbox"
v-model="todo.done"
/>
<span>{{ todo.text }}</span>
<button @click="removeTodo(todo.id)">ลบ</button>
</li>
</ul>
<p class="summary">
เสร็จแล้ว {{ completedCount }} / {{ todos.length }} รายการ
</p>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
interface Todo {
id: number;
text: string;
done: boolean;
}
const newTodo = ref('');
const todos = ref<Todo[]>([
{ id: 1, text: 'เรียน Astro', done: false },
{ id: 2, text: 'สร้าง Vue Component', done: false },
]);
const completedCount = computed(() =>
todos.value.filter((t) => t.done).length
);
let nextId = 3;
function addTodo() {
if (newTodo.value.trim()) {
todos.value.push({
id: nextId++,
text: newTodo.value.trim(),
done: false,
});
newTodo.value = '';
}
}
function removeTodo(id: number) {
todos.value = todos.value.filter((t) => t.id !== id);
}
</script>
<style scoped>
.todo-app {
max-width: 500px;
margin: 0 auto;
padding: 1.5rem;
}
.input-group {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}
input[type="text"] {
flex: 1;
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 4px;
}
.todo-list {
list-style: none;
padding: 0;
}
.todo-list li {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0;
border-bottom: 1px solid #eee;
}
.completed span {
text-decoration: line-through;
color: #999;
}
.summary {
margin-top: 1rem;
color: #666;
}
</style>
ใช้ Vue Component ใน Astro Page
---
import TodoList from '../components/TodoList.vue';
---
<html lang="th">
<head>
<title>Astro + Vue Demo</title>
</head>
<body>
<h1>Vue 3 ใน Astro</h1>
<TodoList client:load />
</body>
</html>
Vue Composables กับ Astro
สร้าง composable สำหรับ fetch data:
// src/composables/useFetch.ts
import { ref, onMounted } from 'vue';
export function useFetch<T>(url: string) {
const data = ref<T | null>(null);
const loading = ref(true);
const error = ref<string | null>(null);
async function fetchData() {
try {
loading.value = true;
const response = await fetch(url);
if (!response.ok) throw new Error('Network error');
data.value = await response.json();
} catch (e) {
error.value = e instanceof Error ? e.message : 'Unknown error';
} finally {
loading.value = false;
}
}
onMounted(fetchData);
return { data, loading, error, refetch: fetchData };
}
ใช้ composable ใน component:
<template>
<div>
<div v-if="loading">กำลังโหลด...</div>
<div v-else-if="error">เกิดข้อผิดพลาด: {{ error }}</div>
<ul v-else>
<li v-for="post in data" :key="post.id">
{{ post.title }}
</li>
</ul>
</div>
</template>
<script setup lang="ts">
import { useFetch } from '../composables/useFetch';
interface Post {
id: number;
title: string;
}
const { data, loading, error } = useFetch<Post[]>('/api/posts');
</script>
Pinia Store กับ Astro
ติดตั้ง Pinia:
npm install pinia
สร้าง app entry point src/pages/_app.ts:
import { createPinia } from 'pinia';
import type { App } from 'vue';
export default (app: App) => {
app.use(createPinia());
};
สร้าง store:
// src/stores/useUserStore.ts
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
export const useUserStore = defineStore('user', () => {
const name = ref('');
const email = ref('');
const isLoggedIn = computed(() => name.value !== '');
function login(userName: string, userEmail: string) {
name.value = userName;
email.value = userEmail;
}
function logout() {
name.value = '';
email.value = '';
}
return { name, email, isLoggedIn, login, logout };
});
Vue Router กับ Astro
Astro จัดการ routing เอง แต่ถ้าต้องการ Vue Router สำหรับ SPA section:
// src/pages/_app.ts
import { createPinia } from 'pinia';
import { createRouter, createWebHistory } from 'vue-router';
import type { App } from 'vue';
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/app', component: () => import('../views/Home.vue') },
{ path: '/app/profile', component: () => import('../views/Profile.vue') },
],
});
export default (app: App) => {
app.use(createPinia());
app.use(router);
};
สรุป
การใช้ Vue 3 กับ Astro ช่วยให้คุณได้ประโยชน์จากทั้งสองโลก Astro ดูแล static generation และ performance ในขณะที่ Vue 3 ด้วย Composition API ช่วยให้สร้าง interactive components ที่ซับซ้อนได้อย่างมีประสิทธิภาพ