Vue 3 Composables ขั้นสูง: รูปแบบการใช้ซ้ำและคำถามสัมภาษณ์งาน 2026

คู่มือ Vue 3 Composables ขั้นสูงฉบับสมบูรณ์: รูปแบบการใช้ซ้ำ การจัดการ Async การ Inject Dependencies การตรวจสอบฟอร์ม และคำถามสัมภาษณ์งานเทคนิคปี 2026

Vue 3 Composables Advanced Patterns

Composable ได้กลายเป็นกลไกหลักในการนำ Logic กลับมาใช้ซ้ำภายในระบบนิเวศของ Vue 3 ตั้งแต่ Composition API ได้รับการยอมรับอย่างกว้างขวางจากชุมชนนักพัฒนาทั่วโลก ความสามารถในการออกแบบ Composable ที่มีความแข็งแกร่ง รองรับ Type Safety และทดสอบได้ง่าย กลายเป็นปัจจัยสำคัญที่แยกนักพัฒนาระดับ Senior ออกจากระดับ Intermediate ในกระบวนการสัมภาษณ์งานเทคนิค ในปี 2026 การเชี่ยวชาญรูปแบบ Composable ขั้นสูงไม่ได้หมายถึงเพียงแค่การใช้ ref และ computed เท่านั้น แต่ครอบคลุมถึงการจัดการ Lifecycle ของ Component อย่างละเอียด การประกอบฟังก์ชัน Reactive เข้าด้วยกัน และการออกแบบสถาปัตยกรรมแบบ Modular สำหรับแอปพลิเคชันขนาดใหญ่

Vue 3.6 ที่เปิดตัวพร้อมกับ Alien Signals ซึ่งเป็นระบบ Reactivity ใหม่ที่มีประสิทธิภาพสูงขึ้นอย่างมาก ทำให้ Composable ทำงานได้เร็วขึ้นและใช้หน่วยความจำน้อยลง ขณะเดียวกัน Vapor Mode ที่กำลังพัฒนาอยู่นั้นจะเปลี่ยนวิธีการ Render Component โดยตรง ซึ่งหมายความว่า Composable ที่ออกแบบมาอย่างดีจะยิ่งมีความสำคัญมากขึ้น เพราะเป็นส่วนที่สามารถพกพาข้ามระหว่าง Virtual DOM และ Vapor Mode ได้โดยไม่ต้องแก้ไขโค้ด

บทความนี้วิเคราะห์รูปแบบ Composable ขั้นสูงของ Vue 3 อย่างเจาะลึก ตั้งแต่โครงสร้างของ Composable ที่ออกแบบมาอย่างดี ไปจนถึงกลยุทธ์การทดสอบ รวมถึงการจัดการ Async และ Dependency Injection แต่ละรูปแบบมาพร้อมตัวอย่างโค้ดที่สามารถนำไปใช้ในโปรเจกต์จริงและในการสัมภาษณ์งานเทคนิคได้ทันที

Composable คืออะไร

Composable คือฟังก์ชันที่ใช้ประโยชน์จาก Composition API ของ Vue เพื่อห่อหุ้มและนำ Logic ที่มี State กลับมาใช้ซ้ำ ตามธรรมเนียมปฏิบัติ Composable จะขึ้นต้นด้วย Prefix use เสมอ (เช่น useCounter, useFetchData) ซึ่งแตกต่างจาก Mixin ใน Vue 2 ตรงที่ Composable มี Type ที่ชัดเจน หลีกเลี่ยงการชนกันของชื่อ และทำให้ Dependency ระหว่างส่วนต่าง ๆ ของโค้ดมีความโปร่งใส

โครงสร้างของ Composable ที่ออกแบบมาอย่างดี

Composable ระดับ Production ที่มีคุณภาพนั้นยึดหลักการออกแบบสถาปัตยกรรมหลายประการ ได้แก่ การกำหนด Type อย่างเข้มงวดสำหรับ Input และ Output การแยกหน้าที่ความรับผิดชอบอย่างชัดเจน และ Interface ที่คาดเดาพฤติกรรมได้ Composable useCounter ต่อไปนี้แสดงให้เห็นถึงธรรมเนียมปฏิบัติพื้นฐานเหล่านี้

useCounter.tstypescript
import { ref, computed, type Ref } from 'vue'

interface UseCounterOptions {
  min?: number
  max?: number
  initialValue?: number
}

interface UseCounterReturn {
  count: Ref<number>
  doubled: Ref<number>
  increment: () => void
  decrement: () => void
  reset: () => void
}

export function useCounter(options: UseCounterOptions = {}): UseCounterReturn {
  const { min = 0, max = Infinity, initialValue = 0 } = options

  const count = ref(initialValue)
  const doubled = computed(() => count.value * 2)

  function increment() {
    if (count.value < max) count.value++
  }

  function decrement() {
    if (count.value > min) count.value--
  }

  function reset() {
    count.value = initialValue
  }

  return { count, doubled, increment, decrement, reset }
}

มีหลายองค์ประกอบที่ควรให้ความสนใจในการ Implement นี้ Interface UseCounterOptions กำหนดสัญญาที่ชัดเจนสำหรับการตั้งค่า ขณะที่ UseCounterReturn ระบุอย่างแม่นยำว่า Composable เปิดเผยอะไรบ้าง การใช้ Destructuring พร้อมค่า Default ภายในตัวฟังก์ชันรับประกันพฤติกรรมที่คาดเดาได้ แม้จะไม่มีการตั้งค่าอย่างชัดเจน

การส่งคืนค่าเป็น Object ที่มีชื่อ (แทนที่จะเป็น Array) ช่วยให้ผู้ใช้งานเลือกเฉพาะ Property ที่ต้องการผ่าน Destructuring ได้ ธรรมเนียมนี้ซึ่งระบบนิเวศ Vue นำมาใช้อย่างแพร่หลาย ช่วยให้โค้ดอ่านง่ายและบำรุงรักษาได้สะดวก

ขอบเขต min และ max แสดงให้เห็นรูปแบบที่พบบ่อยใน Composable ระดับ Production นั่นคือการตรวจสอบข้อจำกัดทางธุรกิจโดยตรงภายใน Logic ที่เป็น Reactive ซึ่งช่วยหลีกเลี่ยงการกระจายกฎเหล่านี้ไปยัง Component ต่าง ๆ ที่เป็นผู้ใช้งาน

Composable แบบ Async พร้อมการจัดการ Error

การจัดการ HTTP Request เป็นหนึ่งในกรณีใช้งาน Composable ที่พบบ่อยที่สุด Composable แบบ Async ที่ออกแบบมาอย่างดีต้องจัดการวงจรชีวิตทั้งหมดของ Request ได้แก่ การโหลด ความสำเร็จ Error และการยกเลิก รูปแบบต่อไปนี้ผสาน watchEffect สำหรับ Reactivity อัตโนมัติ และ AbortController สำหรับการยกเลิก Request อย่างเรียบร้อย

useFetchData.tstypescript
import { ref, watchEffect, onUnmounted, toValue, type Ref, type MaybeRefOrGetter } from 'vue'

interface UseFetchReturn<T> {
  data: Ref<T | null>
  error: Ref<string | null>
  isLoading: Ref<boolean>
  refresh: () => Promise<void>
}

export function useFetchData<T>(
  url: MaybeRefOrGetter<string>
): UseFetchReturn<T> {
  const data = ref<T | null>(null) as Ref<T | null>
  const error = ref<string | null>(null)
  const isLoading = ref(false)
  let abortController: AbortController | null = null

  async function fetchData() {
    // Cancel any in-flight request
    abortController?.abort()
    abortController = new AbortController()

    isLoading.value = true
    error.value = null

    try {
      const response = await fetch(toValue(url), {
        signal: abortController.signal
      })
      if (!response.ok) throw new Error(`HTTP ${response.status}`)
      data.value = await response.json()
    } catch (err) {
      if (err instanceof DOMException && err.name === 'AbortError') return
      error.value = err instanceof Error ? err.message : 'Unknown error'
    } finally {
      isLoading.value = false
    }
  }

  // Re-fetch when URL changes reactively
  watchEffect(() => {
    fetchData()
  })

  onUnmounted(() => abortController?.abort())

  return { data, error, isLoading, refresh: fetchData }
}

การใช้ MaybeRefOrGetter<string> สำหรับ Parameter url มอบความยืดหยุ่นสูงสุด ผู้ใช้งานสามารถส่ง String แบบ Static, ref หรือ Getter ก็ได้ ฟังก์ชัน toValue() จะ Resolve Type พื้นฐานโดยอัตโนมัติ ไม่ว่าจะเป็นค่าดิบหรือค่าที่เป็น Reactive

กลไกการยกเลิกผ่าน AbortController ป้องกัน Race Condition ซึ่งเป็นปัญหาคลาสสิกเมื่อ Request ใหม่ถูกเรียกก่อนที่ Request ก่อนหน้าจะเสร็จสิ้น Hook onUnmounted รับประกันการทำความสะอาด Request ที่กำลังทำงานอยู่เมื่อ Component ถูกทำลาย ช่วยป้องกัน Memory Leak และการอัปเดต State บน Component ที่ถูก Unmount ไปแล้ว

เมธอด refresh ที่เปิดเผยในค่าส่งคืนช่วยให้ Component ผู้ใช้งานสามารถเรียกการโหลดข้อมูลซ้ำด้วยตนเองได้ ซึ่งเป็นรูปแบบที่จำเป็นสำหรับการกระทำของผู้ใช้ เช่น ปุ่ม "โหลดซ้ำ"

Lifecycle Hook ใน Composable

Lifecycle Hook (onMounted, onUnmounted และอื่น ๆ) ต้องถูกเรียกแบบ Synchronous ภายในตัว Composable โดยตรง ห้ามเรียกภายใน Callback แบบ Async หรือ setTimeout เด็ดขาด Vue จะเชื่อมโยง Hook เหล่านี้กับ Instance ของ Component ที่ Active อยู่ ณ เวลาที่เรียก การเรียกแบบล่าช้าอาจทำให้เกิด Error แบบเงียบ หรือผูก Hook กับ Component ที่ผิดได้

การประกอบ Composable เข้าด้วยกัน

พลังที่แท้จริงของ Composable อยู่ที่ความสามารถในการประกอบเข้าด้วยกัน Composable ระดับสูงสามารถ Orchestrate Composable เฉพาะทางหลายตัวเพื่อสร้างฟังก์ชันการทำงานที่ซับซ้อน ขณะที่ยังคงรักษาการแยกหน้าที่ความรับผิดชอบอย่างชัดเจน รูปแบบต่อไปนี้ผสานการค้นหาแบบ Debounce การแบ่งหน้า และการโหลดข้อมูลเข้าด้วยกัน

usePaginatedSearch.tstypescript
import { ref, computed, watch, type Ref } from 'vue'
import { useFetchData } from './useFetchData'
import { useDebouncedRef } from './useDebouncedRef'

interface UsePaginatedSearchReturn<T> {
  query: Ref<string>
  page: Ref<number>
  results: Ref<T[] | null>
  totalPages: Ref<number>
  isLoading: Ref<boolean>
  error: Ref<string | null>
  nextPage: () => void
  prevPage: () => void
}

export function usePaginatedSearch<T>(
  baseUrl: string,
  perPage = 20
): UsePaginatedSearchReturn<T> {
  const query = useDebouncedRef('', 300)
  const page = ref(1)
  const totalPages = ref(1)

  const apiUrl = computed(
    () => `${baseUrl}?q=${encodeURIComponent(query.value)}&page=${page.value}&limit=${perPage}`
  )

  const { data, error, isLoading } = useFetchData<{ items: T[]; total: number }>(apiUrl)

  const results = computed(() => data.value?.items ?? null)

  watch(data, (response) => {
    if (response) {
      totalPages.value = Math.ceil(response.total / perPage)
    }
  })

  // Reset to page 1 when query changes
  watch(query, () => { page.value = 1 })

  function nextPage() {
    if (page.value < totalPages.value) page.value++
  }

  function prevPage() {
    if (page.value > 1) page.value--
  }

  return { query, page, results, totalPages, isLoading, error, nextPage, prevPage }
}

Composable นี้สาธิตเทคนิคการประกอบขั้นสูงหลายประการ useDebouncedRef ห่อหุ้ม Logic การหน่วงเวลา ป้องกันไม่ให้ API ถูกท่วมด้วย Request ในทุกการกดแป้นพิมพ์ computed apiUrl สร้าง URL ใหม่โดยอัตโนมัติเมื่อ Query หรือหน้าเปลี่ยนแปลง ซึ่งจะเรียก useFetchData ให้ดึงข้อมูลใหม่

watch บน query รีเซ็ตหน้ากลับไปที่ 1 ทุกครั้งที่มีการค้นหาใหม่ ซึ่งเป็นพฤติกรรมที่ผู้ใช้คาดหวังแต่มักถูกลืมในการ Implement แบบพื้นฐาน รายละเอียดนี้แสดงให้เห็นประโยชน์ของการห่อหุ้ม Logic ทางธุรกิจไว้ใน Composable แทนที่จะกระจายไปตาม Component ต่าง ๆ

การประกอบ Composable ยึดตามหลักการความรับผิดชอบเดียว (Single Responsibility Principle) คือ useDebouncedRef จัดการ Debouncing, useFetchData จัดการวงจร Request/Response และ usePaginatedSearch Orchestrate ทั้งหมดเข้าด้วยกัน สถาปัตยกรรมนี้ทำให้การทดสอบ Unit Test แต่ละชั้นเป็นอิสระต่อกัน

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

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

Dependency Injection ด้วย provide/inject

สำหรับ State ที่ต้องแชร์ระหว่าง Component ที่อยู่ห่างกันในโครงสร้างต้นไม้ ระบบ provide/inject ของ Vue มอบทางเลือกที่สวยงามแทนการส่ง Prop ลงมาเรื่อย ๆ (Prop Drilling) Composable สามารถห่อหุ้มกลไกนี้เพื่อจัดเตรียม API ที่สะอาดและรองรับ Type Safety

useTheme.tstypescript
import { provide, inject, ref, readonly, type InjectionKey, type Ref } from 'vue'

type Theme = 'light' | 'dark' | 'system'

interface ThemeContext {
  theme: Readonly<Ref<Theme>>
  setTheme: (t: Theme) => void
  resolvedTheme: Readonly<Ref<'light' | 'dark'>>
}

const ThemeKey: InjectionKey<ThemeContext> = Symbol('theme')

export function provideTheme(initial: Theme = 'system') {
  const theme = ref<Theme>(initial)

  const resolvedTheme = computed<'light' | 'dark'>(() => {
    if (theme.value !== 'system') return theme.value
    return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
  })

  function setTheme(t: Theme) {
    theme.value = t
  }

  const context: ThemeContext = {
    theme: readonly(theme),
    setTheme,
    resolvedTheme: readonly(resolvedTheme)
  }

  provide(ThemeKey, context)
  return context
}

export function useTheme(): ThemeContext {
  const context = inject(ThemeKey)
  if (!context) {
    throw new Error('useTheme() requires a parent component to call provideTheme()')
  }
  return context
}

การใช้ InjectionKey ที่มี Type ร่วมกับ Symbol รับประกันความเป็นเอกลักษณ์ของ Key สำหรับการ Inject และให้การอนุมาน Type อัตโนมัติเมื่อเรียกใช้ inject รูปแบบนี้ขจัดความเสี่ยงจากการชนกันของ Key ซึ่งเป็นปัญหาที่เกิดขึ้นกับวิธีการใช้ String เป็น Key

การแยก provideTheme และ useTheme ออกจากกันสร้างขอบเขตทางสถาปัตยกรรมที่ชัดเจน Component ระดับ Root (หรือ Layout) เรียก provideTheme เพื่อเริ่มต้น Context ขณะที่ Component ลูกหลานเรียก useTheme เพื่อใช้งาน การใช้ readonly บน Ref ที่เปิดเผยช่วยป้องกันการเปลี่ยนแปลงค่าโดยไม่ได้ตั้งใจจากฝั่งผู้ใช้งาน

resolvedTheme ที่เป็น Computed แสดงให้เห็นรูปแบบที่พบบ่อย นั่นคือการแปลงค่า Configuration ที่เป็นนามธรรม ('system') ให้เป็นค่าที่เป็นรูปธรรม ('light' หรือ 'dark') โดยอิงจากการตั้งค่าระบบของผู้ใช้ ระดับการ Abstraction นี้ช่วยลดความซับซ้อนของ Logic ใน Component ผู้ใช้งานได้อย่างมาก

Composable สำหรับตรวจสอบความถูกต้องของฟอร์ม

การตรวจสอบความถูกต้องของฟอร์มเป็นกรณีใช้งานที่ซับซ้อนซึ่งได้รับประโยชน์อย่างมากจากการห่อหุ้มไว้ใน Composable รูปแบบต่อไปนี้จัดเตรียมการตรวจสอบแบบ Declarative ที่อิงกฎ พร้อมการจัดการ Error แบบ Reactive และตัวบ่งชี้ความถูกต้องโดยรวม

useFormValidation.tstypescript
import { reactive, computed, type UnwrapNestedRefs } from 'vue'

type ValidationRule<T> = (value: T) => string | true
type FieldRules<T> = { [K in keyof T]?: ValidationRule<T[K]>[] }

interface UseFormReturn<T extends Record<string, any>> {
  fields: UnwrapNestedRefs<T>
  errors: Record<keyof T, string>
  isValid: Ref<boolean>
  validate: () => boolean
  resetErrors: () => void
}

export function useFormValidation<T extends Record<string, any>>(
  initialValues: T,
  rules: FieldRules<T>
): UseFormReturn<T> {
  const fields = reactive({ ...initialValues }) as UnwrapNestedRefs<T>

  const errors = reactive(
    Object.keys(initialValues).reduce(
      (acc, key) => ({ ...acc, [key]: '' }),
      {} as Record<keyof T, string>
    )
  )

  function validate(): boolean {
    let valid = true
    for (const key of Object.keys(rules) as (keyof T)[]) {
      const fieldRules = rules[key] || []
      errors[key] = '' as any
      for (const rule of fieldRules) {
        const result = rule(fields[key])
        if (result !== true) {
          errors[key] = result as any
          valid = false
          break // Stop at first error per field
        }
      }
    }
    return valid
  }

  function resetErrors() {
    for (const key of Object.keys(errors)) {
      (errors as any)[key] = ''
    }
  }

  const isValid = computed(() =>
    Object.values(errors).every((e) => e === '')
  )

  return { fields, errors, isValid, validate, resetErrors }
}

Composable นี้ใช้ reactive แทน ref สำหรับ Object fields ซึ่งช่วยให้เข้าถึง Property ได้โดยตรงโดยไม่ต้องใช้ .value ระบบกฎใช้ธรรมเนียมที่เรียบง่าย กล่าวคือแต่ละกฎส่งคืน true หากสำเร็จ หรือข้อความ Error เป็น String คำสั่ง break หยุดการตรวจสอบที่ Error แรกของแต่ละ Field ช่วยหลีกเลี่ยงการสะสมข้อความ Error ที่ทำให้สับสน

Type FieldRules<T> ใช้ประโยชน์จาก Mapped Types ของ TypeScript เพื่อรับประกันว่ากฎต่าง ๆ สอดคล้องกับ Field ของฟอร์ม แนวทางนี้ตรวจจับ Error ด้าน Type ในขั้นตอน Compile ซึ่งช่วยลด Bug ใน Production

computed isValid จัดเตรียมตัวบ่งชี้แบบ Reactive สำหรับสถานะโดยรวมของฟอร์ม ซึ่งสามารถนำไปใช้โดยตรงเพื่อเปิดหรือปิดปุ่ม Submit โดยไม่ต้องเพิ่ม Logic เพิ่มเติมใน Component

การทดสอบ Composable แบบแยกส่วน

Composable ต้องการ Context ของ Vue ที่ Active อยู่จึงจะทำงานได้อย่างถูกต้อง เนื่องจาก Composable พึ่งพาระบบ Reactivity และ Lifecycle Hook เทคนิคมาตรฐานคือการสร้าง Component Wrapper ขนาดเล็กที่เริ่มต้น Composable ภายใน setup ของมัน โดย Vitest และ Vue Test Utils จัดเตรียมเครื่องมือที่จำเป็น

useCounter.spec.tstypescript
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import { defineComponent, h } from 'vue'
import { useCounter } from './useCounter'

function withSetup<T>(composable: () => T): { result: T; unmount: () => void } {
  let result!: T
  const wrapper = mount(
    defineComponent({
      setup() {
        result = composable()
        return () => h('div')
      }
    })
  )
  return { result, unmount: () => wrapper.unmount() }
}

describe('useCounter', () => {
  it('initializes with default value', () => {
    const { result } = withSetup(() => useCounter())
    expect(result.count.value).toBe(0)
  })

  it('respects min and max boundaries', () => {
    const { result } = withSetup(() =>
      useCounter({ min: 0, max: 3, initialValue: 3 })
    )
    result.increment()
    expect(result.count.value).toBe(3) // Capped at max

    result.count.value = 0
    result.decrement()
    expect(result.count.value).toBe(0) // Capped at min
  })

  it('computes doubled value reactively', () => {
    const { result } = withSetup(() => useCounter({ initialValue: 5 }))
    expect(result.doubled.value).toBe(10)
    result.increment()
    expect(result.doubled.value).toBe(12)
  })
})

ฟังก์ชันยูทิลิตี้ withSetup เป็นรูปแบบที่สามารถนำกลับมาใช้ซ้ำเพื่อทดสอบ Composable ใด ๆ ก็ได้ ฟังก์ชันนี้ Mount Component ขนาดเล็กที่มีหน้าที่เดียวคือรัน Composable ภายใน Context ที่เป็น Reactive อย่างถูกต้อง การส่งคืนเมธอด unmount ช่วยให้สามารถทดสอบพฤติกรรมการทำความสะอาดของ Hook onUnmounted ได้

ชุดทดสอบข้างต้นครอบคลุมสามด้านที่สำคัญ ได้แก่ การเริ่มต้นด้วยค่า Default การปฏิบัติตามข้อจำกัดทางธุรกิจ (ขอบเขต min/max) และ Reactivity ของค่า Computed ความครอบคลุมระดับนี้ถือเป็นมาตรฐานขั้นต่ำที่คาดหวังสำหรับ Composable ระดับ Production

สำหรับ Composable แบบ Async เช่น useFetchData การทดสอบจำเป็นต้องใช้ Mock สำหรับ fetch และใช้ flushPromises() เพื่อรอให้ Promise เสร็จสิ้นก่อนทำการ Assert หัวข้อนี้เป็นคำถามสัมภาษณ์คลาสสิกเกี่ยวกับความเชี่ยวชาญในการทดสอบแบบ Async กับ Vue

VueUse: ไลบรารี Composable อ้างอิงมาตรฐาน

VueUse จัดเตรียม Composable มากกว่า 200 ตัว ครอบคลุมการโต้ตอบกับ Browser, Sensor, Animation และยูทิลิตี้ Reactive ก่อนที่จะพัฒนา Composable แบบกำหนดเอง ควรตรวจสอบก่อนว่า VueUse มีโซลูชันที่ต้องการอยู่แล้วหรือไม่ อย่างไรก็ตาม การทำความเข้าใจรูปแบบที่อยู่เบื้องหลังยังคงมีความสำคัญอย่างยิ่งสำหรับการปรับแต่ง ขยาย หรือ Debug Composable เหล่านั้นในสภาพแวดล้อม Production

คำถามสัมภาษณ์งานที่พบบ่อย

Composable ของ Vue 3 เป็นหัวข้อที่หลีกเลี่ยงไม่ได้ในการสัมภาษณ์งานเทคนิคปี 2026 คำถามต่อไปนี้ครอบคลุมประเด็นที่ผู้สัมภาษณ์มักประเมิน ตั้งแต่ระดับ Intermediate ไปจนถึงระดับ Senior

Composable แตกต่างจาก Mixin อย่างไร

Mixin ใน Vue 2 รวม Options เข้ากับ Component แบบ Implicit ทำให้เกิดการชนกันของชื่อ แหล่งข้อมูลที่ซ่อนอยู่ และขาดการกำหนด Type ในทางกลับกัน Composable เปิดเผย Interface ที่ชัดเจนผ่านค่าส่งคืนที่มี Type รองรับการเปลี่ยนชื่อผ่าน Destructuring และทำให้ Dependency มีความโปร่งใส Composable เหนือกว่าอย่างชัดเจนในด้านการบำรุงรักษา ความสามารถในการทดสอบ และความเข้ากันได้กับ TypeScript

การจัดการ Side Effect ใน Composable ทำอย่างไร

Side Effect ทุกชนิด (Event Listener, Timer, การสมัครสมาชิก WebSocket) ต้องถูกทำความสะอาดใน Hook onUnmounted หรือผ่านกลไก Cleanup ของ watchEffect การละเลยการทำความสะอาดนี้ทำให้เกิด Memory Leak และพฤติกรรมที่คาดเดาไม่ได้เมื่อ Component ถูก Mount ซ้ำ

สามารถเรียก Composable นอก setup() ได้หรือไม่

ไม่ได้ Composable ต้องถูกเรียกแบบ Synchronous ภายในฟังก์ชัน setup() ของ Component หรือภายใน Composable อื่น การเรียกนอก Context นี้ทำให้ Vue ไม่สามารถเชื่อมโยง Lifecycle Hook และ Reactivity กับ Instance ของ Component ที่ถูกต้องได้

การแชร์ State แบบ Global ด้วย Composable ทำได้อย่างไร

มีสองแนวทางหลัก แนวทาง Singleton สร้าง Instance ที่เป็น Reactive ในระดับ Module (ภายนอกฟังก์ชัน Composable) ซึ่งถูกแชร์ระหว่างผู้ใช้งานทุกราย แนวทาง provide/inject ใช้โครงสร้างต้นไม้ของ Component เพื่อเผยแพร่ State แบบ Contextual การเลือกขึ้นอยู่กับกรณีใช้งาน: Singleton สำหรับ State ที่เป็น Global อย่างแท้จริง provide/inject สำหรับ State ที่จำกัดอยู่ในส่วนย่อยของต้นไม้

การทดสอบ Composable ที่ใช้ Lifecycle Hook ทำอย่างไร

เทคนิค withSetup (ที่นำเสนอข้างต้น) สร้าง Component Wrapper ขนาดเล็กที่จัดเตรียม Context ของ Vue ที่จำเป็น สำหรับ Hook เช่น onMounted Component ต้องถูก Mount ใน DOM จริงหรือ DOM เสมือนผ่าน Vue Test Utils Composable ที่เป็น Reactive ล้วน (ไม่มี Lifecycle Hook) สามารถทดสอบได้ง่ายกว่าด้วย effectScope ของ Vue

กลยุทธ์ใดที่ควรใช้สำหรับ Composable แบบ Async ในบริบท SSR

ในบริบท SSR (Nuxt หรือ Vue Server Rendering) Composable แบบ Async ต้องการความระมัดระวังเป็นพิเศษ onMounted ไม่ถูกเรียกบนฝั่ง Server และการเรียก fetch ต้องถูกจัดการผ่านกลไก SSR ของ Framework (เช่น useAsyncData ใน Nuxt) Composable ต้องแยกแยะสภาพแวดล้อมการทำงานและปรับพฤติกรรมให้เหมาะสมตามบริบท

เริ่มฝึกซ้อมเลย!

ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ

บทสรุป

Composable ของ Vue 3 ไม่ใช่เพียงแค่วิวัฒนาการทางไวยากรณ์จาก Mixin เท่านั้น แต่เป็นการเปลี่ยนกระบวนทัศน์ในวิธีการจัดโครงสร้าง Logic ที่ใช้ซ้ำได้ในแอปพลิเคชัน Frontend การเชี่ยวชาญ Composable มอบความได้เปรียบในการแข่งขันอย่างมีนัยสำคัญ ทั้งในสภาพแวดล้อม Production และในการสัมภาษณ์งานเทคนิค

ประเด็นสำคัญที่ควรจดจำ:

  • การกำหนด Type อย่างเข้มงวด: การกำหนด Interface ที่ชัดเจนสำหรับ Options และค่าส่งคืน ช่วยเพิ่มความน่าเชื่อถือและประสบการณ์ของนักพัฒนา
  • การจัดการ Lifecycle: ทำความสะอาด Side Effect ใน onUnmounted เสมอเพื่อป้องกัน Memory Leak
  • การประกอบ: ออกแบบ Composable เฉพาะทางที่สามารถประกอบเข้าด้วยกันได้ แทนที่จะเป็น Composable ขนาดใหญ่ที่ทำทุกอย่าง
  • Reactivity แบบละเอียด: ใช้ประโยชน์จาก MaybeRefOrGetter, toValue และ watchEffect เพื่อเพิ่มความยืดหยุ่นให้สูงสุด
  • Dependency Injection: ใช้ provide/inject พร้อม InjectionKey ที่มี Type สำหรับ State ที่แชร์ในโครงสร้างต้นไม้ของ Component
  • ความสามารถในการทดสอบ: เทคนิค withSetup จัดเตรียม Context ที่เป็น Reactive ขั้นต่ำเพื่อตรวจสอบแต่ละ Composable อย่างเป็นอิสระ
  • การตรวจสอบความถูกต้อง: การห่อหุ้มกฎทางธุรกิจไว้ใน Composable สำหรับ Validation รับประกันความสอดคล้องทั่วทั้งแอปพลิเคชัน

ความสามารถในการออกแบบ ประกอบ และทดสอบ Composable ที่มีความแข็งแกร่ง เป็นสิ่งที่แยกโปรไฟล์ระดับ Senior ออกจากผู้สมัครทั่วไปในตลาด Vue ปี 2026 รูปแบบเหล่านี้คือรากฐานทางเทคนิคที่รองรับแอปพลิเคชัน Vue 3 และ Nuxt 3 ในสภาพแวดล้อม Production

เริ่มฝึกซ้อมเลย!

ทดสอบความรู้ของคุณด้วยตัวจำลองสัมภาษณ์และแบบทดสอบเทคนิคครับ

แท็ก

#vue
#composables
#typescript
#interview

แชร์

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