Vue 3 Reactivity Transform ในปี 2026: $ref, $computed และคำถามสัมภาษณ์งาน

คู่มือเชิงลึกเกี่ยวกับ Reactivity Transform ใน Vue 3 ครอบคลุม $ref, $computed และคำถามสัมภาษณ์ที่พบบ่อยสำหรับนักพัฒนา Vue ระดับสูง

Vue 3 Reactivity Transform $ref และ $computed

Reactivity Transform เป็นฟีเจอร์ทดลองใน Vue 3 ที่มีจุดประสงค์เพื่อทำให้การใช้งาน reactive state ง่ายขึ้นโดยไม่ต้องเข้าถึง .value บน ref การทำความเข้าใจแนวคิดและทางเลือกอื่นยังคงมีคุณค่าในการสัมภาษณ์นักพัฒนา Vue เนื่องจากแสดงให้เห็นถึงความรู้เชิงลึกเกี่ยวกับระบบ reactivity ของ Vue แม้ว่าฟีเจอร์นี้จะถูกลบออกจาก Vue core ตั้งแต่เวอร์ชัน 3.4

สถานะ Reactivity Transform

Reactivity Transform ($ref, $computed) ถูกลบออกจาก Vue 3.4 เป็นต้นไป Vue Macros มี implementation จากชุมชนสำหรับทีมที่ยังต้องการใช้งาน การเข้าใจแนวคิดนี้ยังคงมีคุณค่าสำหรับการสัมภาษณ์เพราะแสดงความรู้เกี่ยวกับวิวัฒนาการของ Vue

ทำความเข้าใจปัญหาที่ Reactivity Transform แก้ไข

ระบบ reactivity ของ Vue 3 อิงกับอ็อบเจกต์ ref และ reactive เมื่อใช้ ref นักพัฒนาต้องเข้าถึงค่าผ่าน property .value ใน script ในขณะที่ใน template การเข้าถึงโดยตรงโดยไม่ต้องใช้ .value เป็นไปได้เนื่องจาก unwrapping อัตโนมัติ

typescript
import { ref, computed } from 'vue'

// วิธีการมาตรฐานกับ .value
const count = ref(0)
const doubled = computed(() => count.value * 2)

function increment() {
  count.value++
  console.log(doubled.value)
}

Reactivity Transform ลบ boilerplate .value ด้วย macro ของ compiler ที่แปลงโค้ดในเวลา build

typescript
// กับ Reactivity Transform (deprecated)
let count = $ref(0)
const doubled = $computed(() => count * 2)

function increment() {
  count++
  console.log(doubled)
}

วิธีการทำงานของ $ref และ $computed

Macro $ref ประกาศตัวแปร reactive ที่สามารถใช้งานได้เหมือนตัวแปร JavaScript ปกติ Compiler ของ Vue แปลงโค้ดนี้เป็นการใช้งาน ref มาตรฐานพร้อม .value ที่เหมาะสม

typescript
// Source code กับ $ref
let username = $ref('')
let isValid = $computed(() => username.length >= 3)

function updateUsername(value: string) {
  username = value
}

// ผลลัพธ์หลังจาก compiler transform
import { ref, computed } from 'vue'

const username = ref('')
const isValid = computed(() => username.value.length >= 3)

function updateUsername(value: string) {
  username.value = value
}

Macro $$ สำหรับ Escape Ref

Macro $$ อนุญาตให้เข้าถึงอ็อบเจกต์ ref ที่อยู่ภายใต้เมื่อจำเป็น เช่น เมื่อส่ง ref ไปยัง composable หรือ watch

typescript
import { watch } from 'vue'

let count = $ref(0)

// ใช้ $$ เพื่อรับ ref object
watch($$(count), (newVal, oldVal) => {
  console.log(`Count เปลี่ยนจาก ${oldVal} เป็น ${newVal}`)
})

// ส่ง ref ไปยัง composable
function useDoubled(countRef: Ref<number>) {
  return computed(() => countRef.value * 2)
}

const doubled = useDoubled($$(count))

Reactive Props Destructure

Vue 3.5 แนะนำ destructuring props แบบ reactive เป็นฟีเจอร์ที่เสถียร ทดแทนบางส่วนของฟังก์ชันการทำงานของ Reactivity Transform สำหรับ props

vue
<script setup lang="ts">
// Vue 3.5+ reactive props destructure
const { count = 0, title } = defineProps<{
  count?: number
  title: string
}>()

// count และ title ยังคง reactive
const doubled = computed(() => count * 2)
</script>

<template>
  <h1>{{ title }}</h1>
  <p>Count: {{ count }}, Doubled: {{ doubled }}</p>
</template>

ก่อน Vue 3.5 การ destructuring props ทำให้สูญเสีย reactivity และต้องใช้ toRefs:

ก่อน Vue 3.5typescript
const props = defineProps<{ count: number }>()
const { count } = toRefs(props) // จำเป็นเพื่อรักษา reactivity

Implementation ทางเลือกด้วย Vue Macros

Vue Macros เป็นคอลเลกชันของ macro ที่ดูแลโดยชุมชน ให้ฟังก์ชันการทำงานของ Reactivity Transform และฟีเจอร์เพิ่มเติมอื่นๆ

vite.config.tstypescript
import Vue from '@vitejs/plugin-vue'
import VueMacros from 'unplugin-vue-macros/vite'

export default defineConfig({
  plugins: [
    VueMacros({
      plugins: {
        vue: Vue(),
      },
    }),
  ],
})
vue
<script setup lang="ts">
// กับ Vue Macros
let count = $ref(0)
let items = $ref<string[]>([])

const total = $computed(() => items.length)
const isEmpty = $computed(() => total === 0)

function addItem(item: string) {
  items = [...items, item]
  count++
}
</script>

Pattern สมัยใหม่โดยไม่ต้องใช้ Reactivity Transform

วิธีการที่แนะนำใน Vue 3.5 ใช้ฟีเจอร์ built-in โดยไม่ต้องใช้ macro ภายนอก

vue
<script setup lang="ts">
import { ref, computed, watch } from 'vue'

// หลักการตั้งชื่อ: ใช้ suffix Ref เพื่อความชัดเจน
const countRef = ref(0)
const itemsRef = ref<string[]>([])

// Computed properties
const total = computed(() => itemsRef.value.length)
const isEmpty = computed(() => total.value === 0)

// Composable กับ reactive state
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
  }
}

const { count, increment, decrement } = useCounter(10)
</script>

เปรียบเทียบ ref vs reactive

การเข้าใจว่าควรใช้ ref เมื่อใดเทียบกับ reactive มักปรากฏในการสัมภาษณ์ Vue

typescript
import { ref, reactive, toRefs } from 'vue'

// ref: สำหรับ primitive values และ single values
const count = ref(0)
const name = ref('Vue')
const isLoading = ref(false)

// reactive: สำหรับ objects ที่มีหลาย properties
const state = reactive({
  user: null as User | null,
  posts: [] as Post[],
  pagination: {
    page: 1,
    perPage: 10
  }
})

// Destructure reactive object ด้วย toRefs
const { user, posts } = toRefs(state)

// ข้อดีของ ref:
// 1. สามารถ reassign ได้ทั้งหมด
// 2. สามารถส่งไปยัง functions โดยไม่สูญเสีย reactivity
// 3. คาดเดาได้มากกว่ากับ TypeScript

// ข้อดีของ reactive:
// 1. ไม่ต้องใช้ .value สำหรับ nested access
// 2. เป็นธรรมชาติมากกว่าสำหรับ complex state objects
// 3. Automatic unwrapping ใน template

Advanced Reactivity Patterns

Pattern ขั้นสูงที่มักถูกถามในการสัมภาษณ์นักพัฒนา Vue ระดับสูง

Custom Ref พร้อม Validation

typescript
import { customRef } from 'vue'

function useDebouncedRef<T>(value: T, delay = 300) {
  let timeout: ReturnType<typeof setTimeout>
  
  return customRef<T>((track, trigger) => ({
    get() {
      track()
      return value
    },
    set(newValue: T) {
      clearTimeout(timeout)
      timeout = setTimeout(() => {
        value = newValue
        trigger()
      }, delay)
    }
  }))
}

// การใช้งาน
const searchQuery = useDebouncedRef('', 500)

Shallow Reactivity สำหรับ Performance

typescript
import { shallowRef, shallowReactive, triggerRef } from 'vue'

// shallowRef: เฉพาะ level แรกที่เป็น reactive
const largeList = shallowRef<Item[]>([])

function updateList(items: Item[]) {
  largeList.value = items // Triggers update
}

function mutateItem(index: number) {
  largeList.value[index].name = 'Updated'
  triggerRef(largeList) // ต้อง trigger แบบ manual
}

// shallowReactive: เฉพาะ properties level แรกที่เป็น reactive
const config = shallowReactive({
  theme: 'dark',
  settings: { /* nested ไม่ reactive */ }
})

พร้อมที่จะพิชิตการสัมภาษณ์ Vue.js / Nuxt.js แล้วหรือยังครับ?

ฝึกฝนด้วยตัวจำลองแบบโต้ตอบ, flashcards และแบบทดสอบเทคนิคครับ

คำถามสัมภาษณ์ Vue Reactivity

คำถามที่ 1: อธิบายความแตกต่างระหว่าง ref และ reactive

คำตอบที่คาดหวัง: ref ห่อค่าใดๆ (primitive หรือ object) ในอ็อบเจกต์ที่มี property .value ในขณะที่ reactive สร้าง proxy แบบ reactive จาก object ref ยืดหยุ่นกว่าเพราะสามารถเก็บ primitive และสามารถ reassign ได้ทั้งหมด reactive เป็นมิตรกับการใช้งานมากกว่าสำหรับ complex nested objects แต่ไม่สามารถ reassign ได้และสูญเสีย reactivity ถ้า destructure โดยไม่มี toRefs

คำถามที่ 2: ทำไม Reactivity Transform ถึงถูกลบออกจาก Vue core?

คำตอบที่คาดหวัง: ทีม Vue ตัดสินใจลบ Reactivity Transform เนื่องจากหลายเหตุผล: เพิ่ม mental complexity โดยการผสม model reactivity สองแบบ ต้องการ tooling พิเศษที่ไม่เสมอไปมีให้ใช้ และทำให้ debugging ยากขึ้นเพราะโค้ดที่เขียนต่างจากโค้ดที่รัน ฟีเจอร์ reactive props destructure ใน Vue 3.5 แก้ไข use case ที่พบบ่อยที่สุดโดยไม่ต้องใช้ macro แบบ global

คำถามที่ 3: วิธีรักษา reactivity เมื่อ destructuring?

typescript
// คำถาม: แก้โค้ดนี้ให้ reactive
const state = reactive({ count: 0, name: 'Vue' })
const { count, name } = state // ไม่ reactive!

// คำตอบ:
import { toRefs } from 'vue'
const state = reactive({ count: 0, name: 'Vue' })
const { count, name } = toRefs(state) // ตอนนี้ reactive แล้ว

// หรือใช้ computed สำหรับ derived values
const count = computed(() => state.count)

คำถามที่ 4: ควรใช้ shallowRef vs ref เมื่อใด?

คำตอบที่คาดหวัง: ใช้ shallowRef เมื่อทำงานกับ large objects หรือ arrays ที่มักถูก replace ทั้งหมดแต่ไม่ค่อยถูก mutate ภายใน สิ่งนี้หลีกเลี่ยง overhead จาก deep reactivity tracking ตัวอย่าง use case: large API response, large datasets สำหรับตาราง หรือ state ที่จัดการโดย external library

คำถามที่ 5: Implement computed writable

typescript
import { ref, computed } from 'vue'

const firstName = ref('John')
const lastName = ref('Doe')

const fullName = computed({
  get() {
    return `${firstName.value} ${lastName.value}`
  },
  set(value: string) {
    const parts = value.split(' ')
    firstName.value = parts[0] || ''
    lastName.value = parts.slice(1).join(' ') || ''
  }
})

// การใช้งาน
fullName.value = 'Jane Smith' // firstName = 'Jane', lastName = 'Smith'

Best Practices Reactivity Vue 3

  1. ให้ความสำคัญกับ ref สำหรับ composables: คืนค่า ref จาก composables เพราะ destructure ง่ายกว่าและยังคง reactive

  2. ใช้ readonly สำหรับข้อมูลที่ไม่ควรถูก mutate: readonly(state) ป้องกันการ mutation ที่ไม่ได้ตั้งใจ

  3. หลีกเลี่ยง deep reactivity ที่ไม่จำเป็น: ใช้ shallowRef หรือ markRaw สำหรับข้อมูลที่ไม่ต้องการ reactive

  4. สม่ำเสมอกับหลักการตั้งชื่อ: เพิ่ม suffix เช่น Ref หรือ State เพื่อความชัดเจน

  5. ใช้ประโยชน์จาก TypeScript: กำหนด types สำหรับ ref generics เพื่อป้องกัน runtime errors

สรุป

แม้ว่า Reactivity Transform ถูกลบออกจาก Vue core แล้ว การเข้าใจแนวคิดเบื้องหลังให้ข้อมูลเชิงลึกที่มีคุณค่าเกี่ยวกับระบบ reactivity ของ Vue ฟีเจอร์สมัยใหม่เช่น reactive props destructure ใน Vue 3.5 ให้ความสะดวกในการใช้งานที่ดีกว่าโดยไม่มีความซับซ้อนเพิ่มเติมจาก macro compiler ในการสัมภาษณ์ ความสามารถในการอธิบาย trade-off ของวิธีการ reactivity ต่างๆ แสดงให้เห็นถึงความเข้าใจอย่างลึกซึ้งเกี่ยวกับ Vue internals

ชาเลนจ์ประจำวัน

คุณหาบั๊กใน Vue.js / Nuxt.js เจอไหม

โค้ดจริงหนึ่งชิ้น บั๊กที่ซ่อนอยู่หนึ่งจุด วันละหนึ่งครั้ง ลองได้โดยไม่ต้องมีบัญชี

Anthony Fillion-Maillet

เขียนโดย

Anthony Fillion-Maillet

ผู้ก่อตั้ง SharpSkill

เป็นนักพัฒนาฟูลสแตกมากว่า 10 ปี ดูแล SharpSkill และรับผิดชอบทุกสิ่งที่เผยแพร่ที่นี่

อัปเดตเมื่อ 9 กันยายน 2569

แท็ก

#vue
#vue 3
#reactivity
#composition api
#interview

แชร์

บทความที่เกี่ยวข้อง