Skip to content

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>