Dark mode
Directives
คำสั่งพิเศษใน template ที่ขึ้นต้นด้วย v-
ใช้สำหรับจัดการ DOM หรือเพิ่มพฤติกรรมพิเศษให้กับ element
v-text
แสดงข้อความภายใน element โดยจะเขียนทับเนื้อหาเดิมทั้งหมด
vue
<template>
<span v-text="message">ข้อความเดิมจะหายไป</span>
<!-- เหมือนกับการใช้ mustache syntax -->
<span>{{ message }}</span>
</template>
<script setup>
import { ref } from 'vue'
const message = ref('Hello Vue!')
</script>
v-html
แสดงผลเนื้อหาที่เป็น HTML จริงๆ ภายใน element ข้อควรระวัง: การใช้ v-html
กับเนื้อหาที่ไม่น่าเชื่อถืออาจเสี่ยงต่อการโจมตีแบบ XSS (Cross-Site Scripting) ควรใช้เมื่อมั่นใจว่าเนื้อหาปลอดภัยเท่านั้น
vue
<template>
<div v-html="rawHtml"></div>
</template>
<script setup>
import { ref } from 'vue'
// ไม่ควรใช้กับ user input โดยตรง!
const rawHtml = ref('<span style="color: red;">This is raw HTML</span>')
</script>
v-show
สลับการแสดงผลของ element โดยการเพิ่ม/ลบ CSS display: none;
element จะยังคงอยู่ใน DOM เสมอ เหมาะสำหรับการสลับที่เกิดขึ้นบ่อยๆ
vue
<template>
<button @click="toggle">Toggle Visibility</button>
<h1 v-show="isVisible">I am visible!</h1>
</template>
<script setup>
import { ref } from 'vue'
const isVisible = ref(true)
const toggle = () => { isVisible.value = !isVisible.value }
</script>
v-if
แสดงผล element ตามเงื่อนไข ถ้าเงื่อนไขเป็น true
element จะถูกสร้างและเพิ่มเข้าไปใน DOM ถ้าเป็น false
element จะถูกลบออกจาก DOM
vue
<template>
<button @click="toggle">Toggle Element</button>
<p v-if="showElement">This element exists in DOM.</p>
</template>
<script setup>
import { ref } from 'vue'
const showElement = ref(true)
const toggle = () => { showElement.value = !showElement.value }
</script>
v-else
ทำงานร่วมกับ v-if
หรือ v-else-if
เพื่อแสดง element เมื่อเงื่อนไขก่อนหน้าเป็น false
ต้องอยู่ติดกับ element ที่มี v-if
หรือ v-else-if
vue
<template>
<div v-if="type === 'A'">Type A</div>
<div v-else>Not Type A</div>
</template>
<script setup>
import { ref } from 'vue'
const type = ref('B')
</script>
v-else-if
ใช้สร้างเงื่อนไขเพิ่มเติมหลังจาก v-if
สามารถใช้ v-else-if
ต่อกันได้หลายอัน และต้องอยู่ติดกับ v-if
หรือ v-else-if
ก่อนหน้า
vue
<template>
<div v-if="score >= 90">Grade A</div>
<div v-else-if="score >= 80">Grade B</div>
<div v-else-if="score >= 70">Grade C</div>
<div v-else>Grade F</div>
</template>
<script setup>
import { ref } from 'vue'
const score = ref(75)
</script>
v-for
วนลูปเพื่อแสดงผล element ซ้ำๆ ตามข้อมูลใน Array หรือ Object ควรใช้ :key
เพื่อระบุ unique identifier ให้กับแต่ละ item เพื่อประสิทธิภาพที่ดีในการอัปเดต DOM
vue
<template>
<!-- วนลูป Array -->
<ul>
<li v-for="(item, index) in items" :key="item.id">
{{ index }}: {{ item.name }}
</li>
</ul>
<!-- วนลูป Object -->
<ul>
<li v-for="(value, key, index) in myObject" :key="key">
{{ index }}. {{ key }}: {{ value }}
</li>
</ul>
<!-- วนลูปตัวเลข -->
<span v-for="n in 5" :key="n">{{ n }} </span>
</template>
<script setup>
import { ref, reactive } from 'vue'
const items = ref([
{ id: 1, name: 'Apple' },
{ id: 2, name: 'Banana' },
{ id: 3, name: 'Cherry' }
])
const myObject = reactive({
title: 'Book Title',
author: 'John Doe',
year: 2024
})
</script>
v-on
ใช้ผูก event listener กับ element เพื่อรอรับ event ต่างๆ เช่น click
, input
, submit
สามารถใช้ shorthand @
แทน v-on:
ได้
vue
<template>
<!-- เต็มรูปแบบ -->
<button v-on:click="handleClick">Click Me (v-on)</button>
<!-- Shorthand -->
<button @click="count++">Click Me (@)</button>
<p>Count: {{ count }}</p>
<!-- รับ event object -->
<button @click="greet('Hello', $event)">Greet</button>
<!-- Event Modifiers -->
<form @submit.prevent="onSubmit"> <!-- .prevent ป้องกัน default submit -->
<button type="submit">Submit</button>
</form>
<div @click.self="handleDivClick"> <!-- .self ทำงานเฉพาะเมื่อคลิกที่ div นี้จริงๆ ไม่ใช่ child -->
Clickable Div (Self)
<button @click="handleButtonClick">Button inside</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const count = ref(0)
const handleClick = () => { alert('Button clicked!') }
const greet = (message, event) => {
alert(message)
console.log(event.target.tagName) // BUTTON
}
const onSubmit = () => { alert('Form submitted!') }
const handleDivClick = () => { alert('Div clicked!') }
const handleButtonClick = () => { alert('Button inside div clicked!') }
</script>
v-bind
ใช้ผูก (bind) ค่าจาก script ไปยัง attribute ของ HTML element หรือ prop ของ component ลูก สามารถใช้ shorthand :
แทน v-bind:
ได้
vue
<template>
<!-- ผูก attribute ปกติ -->
<img v-bind:src="imageUrl" alt="Vue Logo">
<button :disabled="isButtonDisabled">{{ isButtonDisabled ? 'Disabled' : 'Enabled' }}</button>
<!-- ผูก class -->
<p :class="{ active: isActive, 'text-danger': hasError }">Class Binding</p>
<p :class="[isActive ? 'active-class' : '', errorClass]">Array Class Binding</p>
<!-- ผูก style -->
<p :style="{ color: activeColor, fontSize: fontSize + 'px' }">Style Binding</p>
<p :style="[baseStyles, overrideStyles]">Array Style Binding</p>
<!-- ผูก props ให้ component ลูก -->
<!-- <MyComponent :prop-name="parentValue" /> -->
<!-- ผูก object ของ attributes/props -->
<input v-bind="inputAttrs">
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
const imageUrl = ref('https://vuejs.org/images/logo.png')
const isButtonDisabled = ref(true)
const isActive = ref(true)
const hasError = ref(false)
const errorClass = computed(() => hasError.value ? 'text-danger' : '')
const activeColor = ref('red')
const fontSize = ref(16)
const baseStyles = reactive({ fontWeight: 'bold' })
const overrideStyles = reactive({ color: 'blue' })
const inputAttrs = reactive({ type: 'text', placeholder: 'Enter text', required: true })
</script>
<style>
.active { font-weight: bold; }
.text-danger { color: red; }
</style>
v-model
สร้าง two-way data binding ระหว่าง input element (เช่น <input>
, <textarea>
, <select>
) หรือ component ลูก กับตัวแปรใน script ทำให้ค่าใน input กับตัวแปรอัปเดตตรงกันเสมอ
vue
<template>
<!-- Input text -->
<input type="text" v-model="message" placeholder="Text input">
<p>Message: {{ message }}</p>
<!-- Textarea -->
<textarea v-model="multiLineMessage" placeholder="Textarea"></textarea>
<p style="white-space: pre-line;">Multi-line: {{ multiLineMessage }}</p>
<!-- Checkbox (single) -->
<input type="checkbox" id="checkbox" v-model="isChecked">
<label for="checkbox">Checked: {{ isChecked }}</label>
<!-- Checkbox (multiple, bind to array) -->
<div>Checked Names: {{ checkedNames }}</div>
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<!-- Radio -->
<div>Picked: {{ picked }}</div>
<input type="radio" id="one" value="One" v-model="picked">
<label for="one">One</label>
<input type="radio" id="two" value="Two" v-model="picked">
<label for="two">Two</label>
<!-- Select (single) -->
<div>Selected: {{ selected }}</div>
<select v-model="selected">
<option disabled value="">Please select one</option>
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<!-- Select (multiple) -->
<div>Multi Selected: {{ multiSelected }}</div>
<select v-model="multiSelected" multiple style="width:100px;">
<option>A</option>
<option>B</option>
<option>C</option>
</select>
<!-- v-model modifiers -->
<input type="text" v-model.lazy="lazyMessage" placeholder=".lazy (updates on change event)">
<p>Lazy Message: {{ lazyMessage }}</p>
<input type="text" v-model.number="age" placeholder=".number"> <!-- Casts to Number -->
<p>Age Type: {{ typeof age }}</p>
<input type="text" v-model.trim="trimmedMessage" placeholder=".trim"> <!-- Trims whitespace -->
<p>Trimmed: '{{ trimmedMessage }}'</p>
</template>
<script setup>
import { ref } from 'vue'
const message = ref('')
const multiLineMessage = ref('')
const isChecked = ref(true)
const checkedNames = ref(['Jack'])
const picked = ref('One')
const selected = ref('B')
const multiSelected = ref(['A'])
const lazyMessage = ref('')
const age = ref(null)
const trimmedMessage = ref('')
</script>
v-slot
ใช้กำหนดเนื้อหาที่จะส่งเข้าไปใน slot ของ component ลูก ทำให้ component ลูกมีความยืดหยุ่นในการแสดงผลเนื้อหาจาก component แม่ สามารถใช้ shorthand #
แทน v-slot:
ได้
vue
<!-- ParentComponent.vue -->
<script setup>
import BaseLayout from './BaseLayout.vue'
</script>
<template>
<BaseLayout>
<!-- Shorthand #header -->
<template #header>
<h1>Here might be a page title</h1>
</template>
<!-- Default slot (can omit template or use #default) -->
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<!-- Shorthand #footer with slot props -->
<template #footer="{ year }">
<p>Copyright {{ year }} - My Website</p>
</template>
<!-- Dynamic Slot Name -->
<!-- <template #[dynamicSlotName]>...</template> -->
</BaseLayout>
</template>
<!-- BaseLayout.vue (Child Component) -->
<template>
<div class="container">
<header>
<slot name="header">Default Header</slot> <!-- Named Slot -->
</header>
<main>
<slot>Default Content</slot> <!-- Default Slot -->
</main>
<footer>
<slot name="footer" :year="new Date().getFullYear()"> <!-- Named Slot with Props -->
Default Footer
</slot>
</footer>
</div>
</template>
<style>
.container { border: 1px solid #ccc; padding: 10px; margin: 10px; }
header, footer { background-color: #f0f0f0; padding: 5px; }
main { margin-top: 10px; margin-bottom: 10px; }
</style>
v-pre
ข้ามการ compile ของ element นี้และ child elements ทั้งหมด เนื้อหาภายในจะถูกแสดงผลตามที่เขียนไว้ ไม่มีการแทนที่ค่า หรือการทำงานของ directive อื่นๆ
vue
<template>
<span v-pre>{{ this will not be compiled }}</span>
</template>
v-once
Render element และ component เพียงครั้งเดียว หลังจากนั้นการอัปเดตค่า reactive ที่เกี่ยวข้องจะไม่ทำให้ส่วนนี้ re-render อีก เหมาะสำหรับเนื้อหา static ที่ไม่ต้องการอัปเดต
vue
<template>
<h1 v-once>Static Title: {{ initialTitle }}</h1>
<button @click="changeTitle">Change Title (Won't affect v-once)</button>
<p>Current Title: {{ initialTitle }}</p>
</template>
<script setup>
import { ref } from 'vue'
const initialTitle = ref('My Awesome App')
const changeTitle = () => { initialTitle.value = 'Updated Title!' }
</script>
v-memo
ใช้ memoize (จำค่า) ส่วนหนึ่งของ template ถ้าค่า dependency ใน array ที่ระบุไม่เปลี่ยนแปลง ส่วนนั้นจะไม่ถูก re-render ช่วยเพิ่มประสิทธิภาพในกรณีที่มีการคำนวณซับซ้อนหรือ list ขนาดใหญ่
vue
<template>
<div>
<p>Render count: {{ renderCount }}</p>
<button @click="otherValue++">Change Other Value (triggers re-render)</button>
<hr>
<!-- This part only re-renders if `memoValue` changes -->
<div v-memo="[memoValue]">
<p>Memoized Section (value: {{ memoValue }})</p>
<p>Expensive calculation result: {{ performExpensiveCalculation(memoValue) }}</p>
</div>
<button @click="memoValue++">Change Memo Value</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const memoValue = ref(0)
const otherValue = ref(0)
const renderCount = ref(0)
// Important: Ensure refs are cleared if the list changes length
// to avoid memory leaks from old refs
import { onUpdated } from 'vue'
onUpdated(() => { renderCount.value++ })
function performExpensiveCalculation(val) {
console.log('Performing expensive calculation for:', val)
// Simulate delay
let i = 0; while(i < 1e7) { i++ }
return val * 2
}
</script>
v-cloak
ใช้ซ่อน element จนกว่า Vue instance จะพร้อมทำงาน (compile เสร็จ) เพื่อป้องกันไม่ให้เห็น (uncompiled template) แว่บขึ้นมาก่อน มักใช้ร่วมกับ CSS rule [v-cloak] { display: none; }
vue
<template>
<div v-cloak>
{{ message }} <!-- จะไม่เห็นคำว่า {{ message }} ตอนโหลด -->
</div>
</template>
<script setup>
import { ref } from 'vue'
const message = ref('Hello, Vue is ready!')
</script>
<style>
[v-cloak] {
display: none;
}
</style>
Components
<Transition>
<TransitionGroup>
<KeepAlive>
<Teleport>
<Suspense>
Special Elements
<component>
<slot>
<template>
Special Attributes
คำสั่งพิเศษใน template ที่ขึ้นต้นด้วย v-
ใช้สำหรับจัดการ DOM หรือเพิ่มพฤติกรรมพิเศษให้กับ element
key
ใช้เพื่อให้ Vue สามารถติดตาม identity ของแต่ละ node ได้อย่างแม่นยำ โดยเฉพาะเมื่อทำงานกับ lists (v-for
), transitions, หรือการ reuse elements/components
- กับ
v-for
: จำเป็นอย่างยิ่งในการช่วย Vue อัปเดต DOM อย่างมีประสิทธิภาพเมื่อ list เปลี่ยนแปลง (เพิ่ม, ลบ, เรียงลำดับใหม่) ค่าkey
ควรเป็นค่า unique และ stable สำหรับแต่ละ item (เช่น id จาก database) - กับ
<Transition>
: ใช้บังคับให้เกิด transition ใหม่เมื่อค่าkey
เปลี่ยน แม้ว่าจะเป็น element หรือ component เดิม - บังคับให้สร้าง instance ใหม่: เมื่อเปลี่ยน
key
ของ component, Vue จะทำลาย instance เก่าและสร้าง instance ใหม่ทั้งหมด (แทนที่จะ patch instance เดิม)
vue
<template>
<!-- Key in v-for -->
<ul>
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
</ul>
<!-- Key for forcing transition -->
<Transition name="fade">
<p :key="text">{{ text }}</p>
</Transition>
<button @click="text = (text === 'Hello' ? 'Goodbye' : 'Hello')">Change Text</button>
<!-- Key for forcing re-creation -->
<UserProfile :key="userId" :userId="userId" />
<button @click="userId++">Load Next User</button>
</template>
<script setup>
import { ref } from 'vue'
// ... items, text, userId refs/setup ...
const items = ref([{id:1, name:'A'}, {id:2, name:'B'}]);
const text = ref('Hello');
const userId = ref(1);
// Assume UserProfile is a component
// import UserProfile from './UserProfile.vue';
</script>
ref
ใช้สำหรับเข้าถึง DOM element หรือ component instance โดยตรงจาก JavaScript/TypeScript ภายใน <script setup>
vue
<template>
<input type="text" ref="inputElement">
<button @click="focusInput">Focus Input</button>
<MyComponent ref="childComponent" />
<button @click="callChildMethod">Call Child Method</button>
</template>
<script setup>
import { ref, onMounted } from 'vue'
// import MyComponent from './MyComponent.vue' // Assume MyComponent has an exposed method 'doSomething'
// 1. Declare a ref with the same name as the template ref
const inputElement = ref(null)
const childComponent = ref(null)
// 2. Access the element/component after mount
onMounted(() => {
console.log(inputElement.value) // The <input> DOM element
console.log(childComponent.value) // The MyComponent instance
})
function focusInput() {
inputElement.value?.focus() // Optional chaining for safety
}
function callChildMethod() {
// Need to ensure MyComponent explicitly exposes 'doSomething' via defineExpose
// childComponent.value?.doSomething()
}
</script>
<!-- MyComponent.vue -->
<!--
<script setup>
function doSomething() {
console.log('Child method called!');
}
// Expose the method to the parent
defineExpose({ doSomething });
</script>
-->
หมายเหตุ: ค่าของ ref จะเป็น null
ก่อนที่ component จะ mount ต้องเข้าถึงภายใน onMounted
หรือหลังจากนั้น
ref
ใน v-for
:
เมื่อใช้ ref
ใน v-for
, ตัวแปร ref ที่เกี่ยวข้องจะเป็น Array ที่เก็บ element หรือ component instances ทั้งหมด
vue
<template>
<ul>
<li v-for="item in list" :key="item" ref="itemRefs">
{{ item }}
</li>
</ul>
</template>
<script setup>
import { ref, onMounted, watchEffect } from 'vue'
const list = ref([1, 2, 3])
const itemRefs = ref([]) // Initialize as an empty array
onMounted(() => {
// itemRefs.value will be an array containing the <li> DOM elements
console.log(itemRefs.value.map(el => el.textContent)); // ['1', '2', '3']
})
// Important: Ensure refs are cleared if the list changes length
// to avoid memory leaks from old refs
watchEffect(() => {
itemRefs.value = []
}, { flush: 'post' }) // Run after DOM updates
</script>
is
ใช้กับ dynamic components เพื่อระบุว่า component ใดควรจะถูก render สามารถผูกกับชื่อ component (string) หรือ object ของ component ที่ import เข้ามา
vue
<template>
<component :is="currentComponent" />
<button @click="toggleComponent">Toggle Component</button>
</template>
<script setup>
import { ref, shallowRef } from 'vue'
import CompA from './CompA.vue' // Placeholder
import CompB from './CompB.vue' // Placeholder
const useA = ref(true)
// Use shallowRef for components to avoid unnecessary deep reactivity checks
const currentComponent = shallowRef(CompA)
function toggleComponent() {
useA.value = !useA.value
currentComponent.value = useA.value ? CompA : CompB
}
</script>
นอกจากนี้ยังสามารถใช้ is
กับ HTML elements ปกติได้ (เรียกว่า is="..."
attribute) เพื่อแก้ปัญหาข้อจำกัดของ HTML parsing ในบางกรณี (เช่น ใช้ custom component ภายใน <table>
, <ul>
, <select>
) แต่ไม่ค่อยจำเป็นต้องใช้บ่อยนักในปัจจุบัน
html
<!-- อาจจำเป็นถ้า CustomRow เป็น component ที่ render <tr> -->
<table>
<tr is="vue:CustomRow"></tr>
</table>