Dark mode
Getting Started
Vue.js หรือ Nuxt.js ดี?
หากเริ่มต้นโปรเจคใหม่ Nuxt.js อาจเป็นตัวเลือกที่ดีกว่า เพราะมาพร้อมระบบ routing, SSR และ auto-imports ในตัว ช่วยประหยัดเวลาตั้งค่าและทำให้พัฒนาได้เร็วขึ้น แม้เพิ่งเริ่มต้น
Vue.js ก็สามารถเรียนรู้ผ่าน Nuxt ได้เลย
Feature | Vue | Nuxt |
---|---|---|
ประเภท | เฟรมเวิร์ก | เฟรมเวิร์กที่สร้างบน Vue 🚀 |
สถาปัตยกรรม | Single Page App (SPA) | SPA, SSR, SSG, Hybrid 🚀 |
Routing | ต้องตั้งค่าด้วยมือ | อัตโนมัติจากโครงสร้างไฟล์ ✅ |
โครงสร้างโปรเจค | ยืดหยุ่น ⚖️ | กำหนดโครงสร้างมาตรฐาน |
SEO | ต้องตั้งค่าเพิ่ม | พร้อมใช้งานทันที ✅ |
Server-Side Rendering | ต้องตั้งค่าเอง | พร้อมใช้งาน ✅ |
Static Site Generation | ต้องใช้ plugin | พร้อมใช้งาน ✅ |
การตั้งค่า | ต่ำกว่า เรียนรู้ง่าย ✅ | สูงกว่า ต้องเรียนรู้เพิ่ม |
เหมาะสำหรับ | แอปขนาดเล็ก-กลาง ✅ | แอปขนาดใหญ่, เว็บไซต์ ✅ |
ชุมชน | ใหญ่ ✅ | ขนาดกลาง (แต่เติบโตเร็ว) |
เอกสาร | ละเอียด ✅ | ดี แต่มีน้อยกว่า Vue |
การพัฒนาเร็ว | เร็ว ✅ | เร็วแต่ต้องเรียนรู้เพิ่ม |
ความปลอดภัย | ดี | ดีกว่า มีฟีเจอร์ป้องกันเพิ่ม ✅ |
การทดสอบ | ต้องตั้งค่าเอง | มีระบบทดสอบในตัว ✅ |
Basic Concepts
Components (for UI)
ส่วนประกอบพื้นฐานของ Vue ที่ใช้สร้าง UI โดยแบ่งเป็นไฟล์ .vue แยกกัน
ส่วนประกอบของ components
Type of State | คำอธิบาย | Example | กรณีการใช้งาน |
---|---|---|---|
Props | ค่าที่รับเข้ามาในคอมโพเนนต์ | interface Props { msg: string } | ส่งข้อมูลจาก Parent ไปยัง Child component |
Emits | เหตุการณ์ที่ส่งออกจากคอมโพเนนต์ | (e: 'update', value: string): void | แจ้ง Parent เมื่อมีการเปลี่ยนแปลงใน Child |
Slots | ช่องสำหรับส่งเนื้อหา HTML/Component | <template #header>...</template> | สร้าง Component ที่มีโครงสร้างยืดหยุ่น |
Provide/Inject | ส่งค่าข้ามหลายระดับคอมโพเนนต์ | provide('key', value) / inject('key') | ส่งข้อมูลระหว่าง Component ที่ซ้อนกันหลายชั้น |
1. Props - ส่งข้อมูลจาก Parent ไป Child
vue
<!-- ตัวอย่างการใช้ Props -->
<template>
<!-- ส่ง props ชื่อ msg ไปยัง ChildComponent -->
<ChildComponent :msg="message" />
</template>
<script setup lang="ts">
import ChildComponent from './ChildComponent.vue'
// ข้อมูลที่จะส่งเป็น props
const message = ref('สวัสดีจาก Parent')
</script>
vue
<template>
<!-- รับและแสดง props ที่ชื่อ msg -->
<div>{{ msg }}</div>
</template>
<script setup lang="ts">
// กำหนดประเภทของ props ที่จะรับ
interface Props {
msg: string
}
// รับ props
const props = defineProps<Props>()
</script>
2. Emits - ส่งเหตุการณ์จาก Child ไป Parent
vue
<!-- ตัวอย่างการใช้ Emits -->
<template>
<!-- เมื่อคลิกปุ่มจะส่ง event 'update' พร้อมค่า newValue -->
<button @click="$emit('update', newValue)">อัปเดต</button>
</template>
<script setup lang="ts">
// กำหนดประเภทของ events ที่จะส่ง
const emit = defineEmits<{
(e: 'update', value: string): void
}>()
const newValue = 'ข้อมูลที่อัปเดตแล้ว'
</script>
vue
<template>
<!-- รับฟัง event 'update' จาก Child -->
<ChildComponent @update="handleUpdate" />
</template>
<script setup lang="ts">
// ฟังก์ชันที่ทำงานเมื่อได้รับ event 'update'
const handleUpdate = (value: string) => {
console.log('ได้รับค่า:', value)
}
</script>
3. Slots - ส่งเนื้อหา HTML/Component
vue
<template>
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot> <!-- Default slot -->
</main>
</div>
</template>
vue
<template>
<BaseLayout>
<template #header>
<h1>Page Title</h1>
</template>
<p>Main content goes here</p>
</BaseLayout>
</template>
4. Provide/Inject - ส่งค่าข้ามหลายระดับ Component
vue
<template>
<ChildComponent />
</template>
<script setup lang="ts">
import { provide } from 'vue'
provide('theme', 'dark')
</script>
vue
<template>
<div :class="theme">...</div>
</template>
<script setup lang="ts">
import { inject } from 'vue'
const theme = inject('theme', 'light') // Default value 'light'
</script>
Composables (for Refactoring)
ฟังก์ชันที่ใช้แยกและจัดระเบียบ logic ใน Vue
Pattern | ตัวอย่าง | ใช้เมื่อ |
---|---|---|
State Management | useCounter() | ต้องการแยก logic การนับ |
API Calls | useFetch() | การดึงข้อมูลจาก API |
UI Logic | useModal() | การจัดการ modal/popup |
ts
// useCounter.ts
export default function useCounter(initialValue = 0) {
const count = ref(initialValue)
const increment = () => count.value++
const decrement = () => count.value--
const reset = () => count.value = initialValue
return { count, increment, decrement, reset }
}
ts
// ใน component
const { count, increment } = useCounter()
Best Practices:
- ตั้งชื่อไฟล์เป็น
useXXX.ts
- ใช้
ref
/reactive
สำหรับ state - Return object ที่มี methods และ state
- ใช้ TypeScript interface เมื่อจำเป็น
Lifecycle (for Hooks)
วงจรชีวิตของคอมโพเนนต์ ตั้งแต่สร้างจนถึงถูกทำลาย
Hook | เรียกเมื่อ | ตัวอย่างการใช้งาน |
---|---|---|
onBeforeMount | ก่อนที่ component จะถูก mount | ตรวจสอบ authentication, โหลด config จาก localStorage |
onMounted | หลังจาก component ถูก mount | ดึงข้อมูล, ตั้งค่า event listeners, ทำ animation |
onBeforeUpdate | ก่อนที่ DOM จะอัปเดต | บันทึกตำแหน่ง scroll, เปรียบเทียบค่าเก่า/ใหม่ |
onUpdated | หลังจาก DOM อัปเดต | อัปเดต chart/library, ปรับขนาด/ตำแหน่ง |
onBeforeUnmount | ก่อนที่ component จะถูก unmount | ลบ event listeners, ยกเลิก timers |
onUnmounted | หลังจาก component ถูก unmount | ยกเลิก subscriptions, ปิด connections |
onErrorCaptured | เมื่อเกิด error ใน component | แสดงข้อความ error ที่เข้าใจง่าย, ส่ง error logs |
onRenderTracked | เมื่อ render ของ component ถูกติดตาม | ตรวจสอบการเปลี่ยนแปลงของ props หรือ state |
onRenderTriggered | เมื่อ render ของ component ถูกเรียก | ตรวจสอบว่า render เกิดจาก props, state หรืออื่นๆ |
ตัวอย่างการใช้งาน Composition API lifecycle:
vue
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue'
// ประกาศตัวแปร reactive
const count = ref(0)
// ประกาศตัวแปรเก็บ timer
let timer: number
// เมื่อ component ถูก mount
onMounted(() => {
console.log('Component mounted')
// ตั้งค่า timer ให้เพิ่มค่า count ทุกๆ 1 วินาที
timer = setInterval(() => {
count.value++
}, 1000)
})
// เมื่อ component ถูก unmount
onUnmounted(() => {
console.log('Component unmounted')
// ล้าง timer เมื่อ component ถูกลบ
clearInterval(timer)
})
</script>
<template>
<div>
<!-- แสดงค่าของ count -->
<p>Count: {{ count }}</p>
</div>
</template>
Styling (for CSS)
การจัดการสไตล์และ CSS ใน Vue Components
วิธีการ | คำอธิบาย | เมื่อไหร่ที่ควรใช้ |
---|---|---|
Scoped Styles | <style scoped> สไตล์จะมีผลเฉพาะคอมโพเนนต์ปัจจุบัน | วิธีเริ่มต้นสำหรับสไตล์เฉพาะคอมโพเนนต์ |
Utility Frameworks | Tailwind/UnoCSS คลาส utility แบบ atomic | การสร้างเร็ว, โปรเจคขนาดเล็ก-กลาง |
Dynamic Classes | การผูก :class กำหนดคลาสตามเงื่อนไข | การกำหนดสไตล์แบบไดนามิกตามสถานะของคอมโพเนนต์ |
vue
<template>
<div class="p-4 text-red-500 hover:text-blue-500 transition">
<button class="px-4 py-2 bg-blue-500 rounded text-white">
Click Me
</button>
</div>
</template>
vue
<template>
<div
:class="{
'text-red-500': isError,
'font-bold': isImportant
}">
Dynamic Content
</div>
</template>
<script setup>
const isError = ref(true)
const isImportant = ref(false)
</script>
Template (for Rendering)
ไวยากรณ์พื้นฐานใน template ของ Vue สำหรับการเรนเดอร์
Directives
Concept | ตัวอย่าง | กรณีการใช้งาน |
---|---|---|
<div v-if="show"> | การแสดงผลแบบมีเงื่อนไข | |
<li v-for="item in items"> | การแสดงรายการข้อมูล | |
:class="{ active: isActive }" | การกำหนด attribute แบบไดนามิก | |
@click="handleClick" | การจัดการเหตุการณ์ |
vue
<template>
<div>
<p v-if="showMessage">This appears when showMessage is true</p>
<div v-show="isVisible">This toggles visibility</div>
</div>
</template>
vue
<template>
<ul>
<li v-for="(item, index) in items" :key="item.id">
{{ index + 1 }}. {{ item.name }}
</li>
</ul>
</template>
vue
<template>
<div
:class="{ active: isActive, 'text-red': hasError }"
:style="{ fontSize: size + 'px' }"
>
Dynamic Styles
</div>
</template>
vue
<template>
<button
@click="handleClick"
@mouseover="onHover"
@keyup.enter="submit"
>
Interactive Button
</button>
</template>
Components
Concept | ตัวอย่าง | กรณีการใช้งาน |
---|---|---|
<slot></slot> | การประกอบคอมโพเนนต์ | |
<component :is="activeTab"> | การสลับคอมโพเนนต์ | |
<transition name="fade"> | เอฟเฟกต์การเปลี่ยนภาพ |
vue
<!-- Parent -->
<template>
<ChildComponent>
<template #header>
<h1>Custom Header</h1>
</template>
Default Slot Content
</ChildComponent>
</template>
<!-- Child -->
<template>
<div>
<slot name="header"></slot>
<slot></slot>
</div>
</template>
vue
<template>
<component :is="currentView" />
</template>
<script setup>
const currentView = shallowRef(Home)
</script>
vue
<template>
<transition name="fade" mode="out-in">
<div v-if="show" key="content">
Content to animate
</div>
</transition>
</template>
<style>
.fade-enter-active, .fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
</style>
State (for Data)
การจัดการสถานะข้อมูลในแอปพลิเคชัน Vue
Type of State | ใช้สำหรับ | ตัวอย่างการใช้งาน |
---|---|---|
การจัดการ state ร่วมกันหลาย components | const store = useStore() | |
การจัดการ state ใน component เดียว | const count = ref(0) |
ts
// สร้าง store
const store = useStore()
// ใช้ state จาก store
const products = computed(() => store.products)
// เรียก action
const loadProducts = async () => {
await store.loadProducts()
}
ts
// สร้าง reactive state
const searchQuery = ref('') // สำหรับ primitive values
const user = reactive({ // สำหรับ objects
name: 'John',
age: 30,
preferences: {
darkMode: true
}
})
// ใช้ computed สำหรับ derived state
const isAdult = computed(() => user.age >= 18)
WARNING
- อย่าแก้ไข state โดยตรงนอกเหนือจาก action ใน Pinia
- สำหรับ Pinia ควรใช้ action เท่านั้นในการแก้ไข state
- ใช้
markRaw
เมื่อต้องการเก็บ object ที่ไม่ต้องการให้เป็น reactive
TIP
- ใช้
computed
สำหรับค่าที่คำนวณจาก state อื่น - ใช้
watchEffect
สำหรับ side effects ที่ขึ้นกับ reactive state
Events (for Interaction)
ระบบจัดการเหตุการณ์และ interaction ในคอมโพเนนต์
Feature | คำอธิบาย | Example | กรณีการใช้งาน |
---|---|---|---|
Native Events | รับฟัง DOM events ด้วย v-on หรือ @ | @click="handleClick" | การจัดการกับ DOM events |
Custom Events | การสื่อสารจาก child ไป parent ด้วย $emit | this.$emit('custom-event') | การสื่อสารระหว่าง components |
Event Modifiers | คำต่อท้ายสำหรับจัดการ event ทั่วไป | @click.stop.prevent | การจัดการกับ event bubbling |
Key Modifiers | รับฟังปุ่มคีย์บอร์ดเฉพาะ | @keyup.enter="submit" | การใช้งานกับ keyboard shortcuts |
Mouse Modifiers | event ปุ่มเมาส์เฉพาะ | @click.right="contextMenu" | การใช้งานกับ context menu |
System Modifiers | ปุ่ม Ctrl, Alt, Shift, Meta | @click.ctrl="shortcut" | การใช้งานกับ keyboard shortcuts |
vue
<template>
<!-- Native Event ตัวอย่างการใช้งาน event พื้นฐาน -->
<button @click="handleClick">
Click Me
</button>
<form @submit.prevent="handleSubmit">
<input @input="onInput" />
</form>
</template>
vue
<!-- Child -->
<template>
<button @click="$emit('custom-event', data)">
Send Event
</button>
</template>
<!-- Parent -->
<template>
<ChildComponent @custom-event="handleEvent" />
</template>
vue
<template>
<!-- Stop event propagation -->
<div @click.stop="doSomething"></div>
<!-- Prevent default -->
<form @submit.prevent="submit"></form>
<!-- Key modifier -->
<input @keyup.enter="submit" />
</template>
APIs
Global APIs
APIs | คำอธิบาย | ตัวอย่างการใช้งาน | กรณีการใช้งาน |
---|---|---|---|
เริ่มต้นแอป Vue ใหม่ | const app = createApp(App) | เมื่อต้องการสร้างแอป Vue ใหม่ | |
รอการอัปเดต DOM เสร็จสิ้นก่อนทำงานต่อ | nextTick(() => { /* DOM updated */ }) | เมื่อต้องทำงานกับ DOM หลังการอัปเดตข้อมูล | |
ตรวจสอบเวอร์ชัน Vue ที่ใช้งานอยู่ | console.log(Vue.version) | เมื่อต้องการตรวจสอบความเข้ากันได้ของฟีเจอร์ | |
กำหนดคอมโพเนนต์พร้อมการตรวจสอบประเภทข้อมูล | export default defineComponent({...}) | เมื่อสร้างคอมโพเนนต์ใน TypeScript |
Single File Components
ฟีเจอร์ | วิธีใช้งาน |
---|---|
เขียน logic ในแท็ก <script setup> | |
เพิ่ม scoped ในแท็ก <style> | |
กำหนดแท็ก <docs> หรือ <tests> |
Built-in Components
APIs | คำอธิบาย | ตัวอย่างการใช้งาน | กรณีการใช้งาน |
---|---|---|---|
จัดการการเปลี่ยนระหว่างสถานะ | <Transition name="fade">...</Transition> | เมื่อต้องการ animation ระหว่างการแสดง/ซ่อน | |
เก็บสถานะคอมโพเนนต์เมื่อซ่อน | <KeepAlive><Component /></KeepAlive> | เมื่อต้องการเก็บสถานะระหว่างการสลับแท็บ | |
ย้าย element ไปตำแหน่งอื่นใน DOM | <Teleport to="#modals"><Modal /></Teleport> | เมื่อต้องการแสดง component นอก hierarchy ปกติ | |
แสดง loading รอข้อมูล | <Suspense><AsyncComponent /></Suspense> | เมื่อโหลด component แบบ asynchronous |
Composition API
ฟังก์ชัน | คำอธิบาย | ตัวอย่างการใช้งาน |
---|---|---|
สร้าง reactive references หรือ objects | const count = ref(0) | |
computed properties และ watchers | const double = computed(() => count.value * 2) | |
Dependency injection สำหรับคอมโพเนนต์ | provide('key', value) / const val = inject('key') | |
lifecycle hooks สำหรับคอมโพเนนต์ | onMounted(() => { console.log('mounted') }) | |
แปลง property ของ reactive object เป็น ref | const name = toRef(user, 'name') | |
แปลง reactive object เป็น plain object ของ refs | const { name, age } = toRefs(user) |
Advanced Patterns
APIs | คำอธิบาย | ตัวอย่างการใช้งาน | กรณีการใช้งาน |
---|---|---|---|
directives กำหนดเองสำหรับจัดการ DOM | v-focus สำหรับ auto-focus input | เมื่อต้องการจัดการ DOM โดยตรง | |
plugins สำหรับฟีเจอร์ระดับ global | app.use(myPlugin) | เมื่อต้องการเพิ่มฟีเจอร์ที่ใช้ทั่วทั้งแอป | |
ฟังก์ชันสำหรับการ render แบบกำหนดเอง | h('div', { id: 'foo' }, 'hello') | เมื่อต้องการความยืดหยุ่นในการ render สูง | |
ไวยากรณ์ JSX สำหรับการ render | const vnode = <div id="foo">{hello}</div> | เมื่อต้องการเขียน template แบบ JavaScript |
Best Practices
Naming Convention
Type | Naming Convention | Example |
---|---|---|
Components | PascalCase | UserProfile.vue |
Composition Functions | use + camelCase | useFetchData |
Props | camelCase (script), kebab-case (template) | userName , user-name |
Events | kebab-case | @update-value |
Pinia Stores | use + Store + camelCase | useUserStore |
Performance
Rendering Optimization
Technique | คำอธิบาย | Example |
---|---|---|
Conditional Rendering | v-if: ลบออกจาก DOM เมื่อเป็น false v-show: ซ่อนด้วย CSS สำหรับการสลับบ่อยครั้ง | <div v-if="show"> <div v-show="active"> |
List Rendering | ใช้ ID ที่ไม่ซ้ำกันเป็น key สำหรับการแสดงผลรายการที่มีประสิทธิภาพ | <li v-for="item in items" :key="item.id"> |
Virtual Scrolling | render only visible parts, reduce memory usage | <VirtualScroller :items="bigData" /> |
Data Handling
Technique | คำอธิบาย | Example |
---|---|---|
Computed | cache value for dependencies | const total = computed(() => price * qty) |
Shallow Refs | disable deep reactivity when not needed | const bigObj = shallowRef({...}) |
Debounce | limit frequent function calls | <input @input="debounceFn"> |
Code Splitting
Technique | คำอธิบาย | Example |
---|---|---|
Lazy Load | load components on demand | const Modal = defineAsyncComponent(() => import('./Modal.vue')) |
Route Split | split code by route for smaller initial bundle | component: () => import('./UserProfile.vue') |
Type Safety
วิธีปฏิบัติ | ประโยชน์ | ตัวอย่าง |
---|---|---|
เปิดโหมด Strict | ตรวจสอบประเภทข้อมูลอย่างเข้มงวด | "strict": true ใน tsconfig.json |
กำหนด Custom Types | ลดข้อผิดพลาดจากโครงสร้างข้อมูล | interface User { name: string } |
ใช้ Utility Types | เพิ่มความยืดหยุ่นในการใช้งาน | Partial<User> สำหรับข้อมูลบางส่วน |
Component Organization
vue
<!-- MyComponent.vue -->
<script setup lang="ts">
// 1. Imports (external libraries)
import { ref } from 'vue'
import { useRouter } from 'vue-router'
// 2. Type Definitions
interface Props {
id: number
title: string
}
// 3. Component Props & Emits
defineProps<Props>()
const emit = defineEmits<{
(e: 'submit', value: string): void
}>()
// 4. Composables
const { user } = useAuth()
// 5. Reactive State
const count = ref(0)
const isLoading = ref(false)
// 6. Lifecycle Hooks
onMounted(() => {
fetchData()
})
// 7. Methods/Functions
async function fetchData() {
isLoading.value = true
// ...
isLoading.value = false
}
</script>
<template>
<!--
Template Organization:
1. Root element (1 element only)
2. Conditional Rendering (v-if/v-show)
3. List Rendering (v-for)
4. Event Handlers
5. Dynamic Classes/Styles
-->
<div class="container">
<h1>{{ title }}</h1>
<button @click="emit('submit', 'data')">
Submit
</button>
</div>
</template>
<style scoped>
/*
CSS Organization:
1. Layout (position, grid, flex)
2. Box Model (margin, padding, border)
3. Typography
4. Visual (colors, shadows)
5. Animations
*/
.container {
padding: 1rem;
}
</style>
Best Practices:
- Order script sections by importance (top-down)
- Separate template sections with comments
- Group CSS by purpose
- Use meaningful names for variables and functions
Project Structure
ts
// 1. Atomic Design Structure
src/
├── components/
│ ├── atoms/ // Button, Icon, Input
│ ├── molecules/ // SearchBar, Card
│ ├── organisms/ // Header, ProductGrid
│ └── templates/ // MainLayout, AuthLayout
// 2. Feature-based Structure
src/
├── features/
│ ├── auth/ // LoginForm, RegisterForm
│ ├── dashboard/ // StatsCard, Chart
│ └── products/ // ProductList, ProductDetail
// 3. Core Files
src/
├── App.vue // Root component
├── main.ts // App entry
└── vite.config.ts // Build config