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

เจาะลึก Composables ขั้นสูงของ Vue 3 ตั้งแต่รูปแบบที่นำกลับมาใช้ซ้ำได้ การจัดการ Error แบบ Async การ Inject Dependencies ไปจนถึง Form Validation พร้อมคำถามสัมภาษณ์เทคนิคที่อัปเดตสำหรับปี 2026

รูปแบบขั้นสูงของ Composables ใน Vue 3 พร้อมตัวอย่างโค้ด TypeScript และแผนภาพ Reactive Composition

Composition API ของ Vue 3 ได้เปลี่ยนแปลงวิธีที่นักพัฒนาจัดระเบียบและนำ Logic กลับมาใช้ซ้ำในแอปพลิเคชัน Frontend อย่างสิ้นเชิง หัวใจสำคัญของการเปลี่ยนแปลงนี้คือ Composables ซึ่งเป็นฟังก์ชันที่ห่อหุ้ม Reactive State, Business Logic และ Side Effects ไว้ในหน่วยที่เป็นอิสระและนำกลับมาใช้ซ้ำได้ ต่างจาก Mixins ของ Vue 2 ที่มักก่อให้เกิดปัญหาชื่อซ้ำกันและ Dependencies ที่ซ่อนอยู่ Composables นั้นมอบความโปร่งใสอย่างเต็มที่ รองรับ Static Typing ด้วย TypeScript และมีการ Compose ที่ชัดเจน

ในปี 2026 การเชี่ยวชาญรูปแบบขั้นสูงของ Composables ได้กลายเป็นข้อกำหนดพื้นฐานสำหรับตำแหน่งนักพัฒนา Vue ระดับกลางถึงระดับอาวุโส บทความนี้จะสำรวจรูปแบบที่ถูกถามบ่อยที่สุดในการสัมภาษณ์เทคนิค ตั้งแต่โครงสร้างของ Composable ที่ออกแบบมาอย่างดี ไปจนถึงการ Inject Dependencies ด้วย provide/inject รวมถึงกลยุทธ์การทดสอบที่ตรวจสอบความถูกต้องของ Reactivity ในแต่ละส่วน

Composable ใน Vue 3 คืออะไร

Composable คือฟังก์ชันที่ใช้ Composition API ของ Vue เพื่อห่อหุ้มและนำ Logic ที่มี State กลับมาใช้ซ้ำ ตามธรรมเนียมแล้ว ชื่อของ Composable จะขึ้นต้นด้วย use (เช่น useCounter, useFetch) แต่ละ Composable จะคืนค่า Reactive Refs, Computed Values และฟังก์ชันที่ Component สามารถนำไปใช้ได้โดยตรง โดยไม่ต้องพึ่งพา Inheritance หรือ Coupling ที่ซ่อนอยู่

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

Composable ที่มีประสิทธิภาพจะยึดหลักการที่ชัดเจน ได้แก่ รับค่า Configuration ผ่าน Object ที่มี Type กำหนดไว้ คืนค่าผ่าน Interface ที่ชัดเจน และรักษา Internal State ให้แยกออกจากผู้ใช้งานรายอื่น ตัวอย่างต่อไปนี้แสดงการสร้าง Counter ที่มีขอบเขตที่ปรับแต่งได้และ Computed Value ที่ได้มาจาก State

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 }
}

มีหลายประเด็นในรูปแบบนี้ที่ควรให้ความสนใจ Interface UseCounterReturn กำหนด Contract ของ Composable อย่างชัดเจน ซึ่งช่วยให้ Editor แนะนำโค้ดได้อัตโนมัติและป้องกันการเปลี่ยนแปลง Public API โดยไม่ตั้งใจ Object ของ Options ที่มีค่าเริ่มต้นช่วยให้สามารถปรับแต่ง Configuration ได้อย่างยืดหยุ่นโดยไม่ทำให้ Function Signature ซับซ้อนเกินไป แต่ละครั้งที่เรียกใช้ useCounter() จะสร้าง State Instance ใหม่ที่เป็นอิสระ ซึ่งขจัดปัญหา Shared State ที่เคยเกิดขึ้นกับ Mixins ได้อย่างสิ้นเชิง

รูปแบบ "Options เป็น Input, Typed Interface เป็น Output" นี้เป็นรากฐานสำหรับการสร้าง Composables ที่ซับซ้อนยิ่งขึ้น

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

การทำงานผ่านเครือข่ายเป็นหนึ่งในกรณีการใช้งานที่พบบ่อยที่สุดสำหรับ Composables รูปแบบต่อไปนี้สร้างฟังก์ชัน Fetching แบบ Reactive ที่ยกเลิก Request ที่กำลังดำเนินอยู่โดยอัตโนมัติ จัดการ Error อย่างละเอียด และตอบสนองต่อการเปลี่ยนแปลงของ URL ต้นทาง

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 }
}

Composable นี้นำเสนอแนวคิดขั้นสูงหลายประการ Type MaybeRefOrGetter<string> สามารถรับทั้งค่าคงที่ Reactive Refs หรือ Getter Functions ซึ่งมอบความยืดหยุ่นสูงสุดให้กับผู้ใช้งาน ฟังก์ชัน toValue() จะดึงค่าที่อยู่ภายในออกมาโดยไม่ขึ้นกับประเภทของ Input การใช้ AbortController ช่วยป้องกัน Race Conditions โดยยกเลิก Request ที่ล้าสมัยเมื่อ URL เปลี่ยนแปลงก่อนที่ Response ก่อนหน้าจะมาถึง

watchEffect จะสร้าง Reactive Tracking โดยอัตโนมัติ กล่าวคือ Ref หรือ Getter ใดก็ตามที่ถูกเข้าถึงภายใน Callback จะกลายเป็น Dependency หาก URL เป็น Ref และค่าของมันเปลี่ยนแปลง Effect จะถูกเรียกใช้ซ้ำโดยอัตโนมัติ Hook onUnmounted รับประกันการทำความสะอาดทรัพยากรเมื่อ Component ถูกทำลาย

Lifecycle Hooks ใน Composables

Hooks อย่าง onMounted, onUnmounted และ watchEffect จะทำงานได้อย่างถูกต้องก็ต่อเมื่อ Composable ถูกเรียกใช้ภายใน setup() หรือ <script setup> เท่านั้น การเรียกใช้ Composable นอกบริบทนี้ (เช่น ใน Async Callback ที่ถูกเลื่อนออกไป) จะส่งผลให้เกิดคำเตือนขณะ Runtime และ Effects ที่ไม่ถูกลงทะเบียน ข้อจำกัดนี้เป็นหนึ่งในคำถามที่ถูกถามบ่อยที่สุดในการสัมภาษณ์เทคนิคของ Vue

การ Compose Composables เข้าด้วยกัน

พลังที่แท้จริงของ Composables จะปรากฏขึ้นเมื่อนำมารวมกัน ตัวอย่างต่อไปนี้สร้างระบบค้นหาแบบแบ่งหน้าโดยนำ useFetchData และ useDebouncedRef สมมติมาใช้ซ้ำ แสดงให้เห็นว่าการ Compose สามารถสร้างฟังก์ชันการทำงานที่ซับซ้อนจากชิ้นส่วนที่เรียบง่ายได้อย่างไร

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 }
}

การ Compose เกิดขึ้นในหลายระดับ useDebouncedRef คืนค่า Ref ที่มี Debounce ในตัว ซึ่งช่วยป้องกันการส่ง Request มากเกินไประหว่างที่ผู้ใช้พิมพ์ useFetchData รับ apiUrl ในรูปแบบ computed หมายความว่าการเปลี่ยนแปลงใดก็ตามใน query หรือ page จะคำนวณ URL ใหม่และส่ง Request ใหม่โดยอัตโนมัติ Watcher บน query จะรีเซ็ตการแบ่งหน้ากลับไปที่หน้า 1 ทุกครั้งที่คำค้นหาเปลี่ยนแปลง

รูปแบบนี้แสดงให้เห็นหลักการพื้นฐานที่สำคัญ นั่นคือแต่ละ Composable แก้ปัญหาเฉพาะเจาะจงและการ Compose จะเชื่อมต่อทุกส่วนเข้าด้วยกันโดยไม่มี Coupling ในการสัมภาษณ์เทคนิค การอธิบายห่วงโซ่ Reactivity นี้ (query เปลี่ยน -> apiUrl คำนวณใหม่ -> useFetchData เรียกใช้ซ้ำ -> results อัปเดต) แสดงถึงความเข้าใจอย่างลึกซึ้งต่อระบบ Reactive ของ Vue

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

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

Dependency Injection ด้วย provide/inject

State บางประเภทจำเป็นต้องแชร์ข้ามลำดับชั้นของ Component โดยไม่ต้องส่ง Props ลงไปทีละชั้น ระบบ provide/inject ของ Vue เมื่อใช้ร่วมกับ Composables จะสร้างรูปแบบ Context ที่มี Type กำกับและปลอดภัย

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
}

รูปแบบนี้แยก Provision ออกจาก Consumption อย่างชัดเจน Component หลัก (หรือ Layout) จะเรียก provideTheme() เพียงครั้งเดียว และ Component ลูกที่อยู่ลึกลงไปในลำดับชั้นใดก็ตามสามารถเข้าถึง Context ได้ผ่าน useTheme() โดยไม่ต้องคำนึงถึงความลึกของ Component Tree การใช้ InjectionKey<ThemeContext> กับ Symbol รับประกัน Type Safety ในขณะ Compile

ฟังก์ชัน readonly() ห่อหุ้ม Refs ที่เปิดเผยออกมาเพื่อป้องกันการแก้ไขโดยตรงจาก Component ที่เป็นผู้ใช้งาน มีเพียง setTheme() เท่านั้นที่สามารถเปลี่ยนแปลง Theme ได้ ซึ่งบังคับให้เกิดการไหลของข้อมูลแบบทิศทางเดียว Property resolvedTheme จะแปลงค่า 'system' โดยตรวจสอบ Media Query ของเบราว์เซอร์ ทำให้ได้ค่าที่ชัดเจน ('light' หรือ 'dark') ที่ Component สามารถนำไปใช้สำหรับการจัดรูปแบบได้ทันที

ในการสัมภาษณ์ รูปแบบนี้มักถูกเปรียบเทียบกับ React Context หรือ Stores ของ Pinia ความแตกต่างสำคัญคือ provide/inject ทำงานที่ระดับ Component Tree (ไม่ใช่ Global) และไม่ต้องการ Dependencies ภายนอก

Composable สำหรับ Form Validation

Form Validation เป็นโดเมนที่ Composables แสดงศักยภาพได้อย่างโดดเด่น โดยแทนที่ Logic ที่ซ้ำซ้อนด้วยรูปแบบ Declarative ที่อิงตามกฎ

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 นี้รับค่าเริ่มต้นและ Map ของกฎ Validation สำหรับแต่ละ Field แต่ละกฎเป็นฟังก์ชันที่คืนค่า true หากค่านั้นถูกต้อง หรือคืนค่า String ที่เป็นข้อความ Error Method validate() จะวนซ้ำผ่านกฎทั้งหมดและหยุดที่ Error แรกของแต่ละ Field เพื่อหลีกเลี่ยงการแสดงข้อความหลายรายการพร้อมกันซึ่งอาจทำให้ผู้ใช้สับสน

การใช้ reactive() แทน ref() สำหรับ Fields และ Errors ช่วยให้การเข้าถึงใน Template ง่ายขึ้น เช่น fields.email แทนที่จะเป็น fields.value.email Computed Property isValid จะอัปเดตโดยอัตโนมัติเมื่อ Error ใดก็ตามเปลี่ยนแปลง ทำให้สามารถเปิดหรือปิดปุ่ม Submit แบบ Reactive ได้

รูปแบบนี้สามารถขยายได้ง่าย ไม่ว่าจะเป็นการเพิ่มกฎ Async (ตรวจสอบว่า Email ซ้ำหรือไม่) การ Validate ข้ามฟิลด์ (ยืนยันรหัสผ่าน) และข้อความ Error ที่รองรับหลายภาษา โดยไม่ต้องแก้ไขโครงสร้างพื้นฐานของ Composable

การทดสอบ Composables

Composables ที่ใช้ Reactive APIs ของ Vue ต้องการ Component Context จึงจะทำงานได้อย่างถูกต้อง ฟังก์ชันช่วยเหลือ withSetup แก้ปัญหานี้โดยสร้าง Component ขั้นต่ำที่เรียกใช้ Composable ภายใน setup()

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 เป็นรูปแบบมาตรฐานใน Ecosystem ของ Vue สำหรับการทดสอบ Composables อย่างอิสระ โดย Mount Component Wrapper ที่เรียกใช้ Composable ภายใน setup() ซึ่งให้การเข้าถึงผลลัพธ์และฟังก์ชัน unmount() สำหรับจำลองการทำลาย Component

แต่ละ Test ตรวจสอบแง่มุมเฉพาะ ได้แก่ การเริ่มต้นด้วยค่าเริ่มต้น การเคารพขอบเขตที่กำหนดไว้ และ Reactivity ของ Computed Values Test ที่สามมีความสำคัญเป็นพิเศษเพราะตรวจสอบว่า doubled อัปเดตโดยอัตโนมัติเมื่อ count เปลี่ยนแปลงผ่าน increment() ซึ่งยืนยันว่า Reactive Graph ทำงานได้อย่างถูกต้อง

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

VueUse: ไลบรารี Composables อ้างอิง

VueUse (vueuse.org) เป็นไลบรารี Utility Composables ที่ได้รับความนิยมสูงสุดใน Ecosystem ของ Vue โดยมีฟังก์ชันมากกว่า 200 รายการที่ครอบคลุม Browser Sensors, Animations, State, Network และ Storage ก่อนที่จะสร้าง Composable เอง การตรวจสอบว่า VueUse มีโซลูชันที่ผ่านการทดสอบและปรับแต่งประสิทธิภาพแล้วหรือไม่ ถือเป็นแนวปฏิบัติที่ดีที่ผู้สัมภาษณ์ให้คุณค่า

คำถามที่พบบ่อยในการสัมภาษณ์เทคนิค

คำถามต่อไปนี้ปรากฏขึ้นอย่างสม่ำเสมอในกระบวนการคัดเลือกสำหรับตำแหน่ง Vue ระดับกลางถึงระดับอาวุโสในปี 2026

1. Composable แตกต่างจาก Mixin อย่างไร Mixins จะรวม Properties เข้ากับ Component แบบ Implicit ทำให้เกิดปัญหาชื่อซ้ำกันและยากต่อการติดตามว่า Property แต่ละตัวมาจากไหน ในขณะที่ Composables คืนค่าแบบ Explicit มี Type กำกับครบถ้วนด้วย TypeScript และสามารถเปลี่ยนชื่อตัวแปรได้เมื่อ Destructure Return Value

2. ทำไม Composables จึงต้องถูกเรียกใช้ภายใน setup() Vue จะเชื่อมโยง Lifecycle Hooks (onMounted, onUnmounted, watch, watchEffect) เข้ากับ Component Instance ที่กำลังทำงานอยู่ในระหว่าง setup() การเรียกใช้ Composable นอกบริบทนี้จะทำลายการเชื่อมโยงดังกล่าว ส่งผลให้ Effects ไม่ถูกลงทะเบียนและไม่ถูกทำความสะอาด

3. จะแชร์ State ระหว่างหลาย Component ด้วย Composables ได้อย่างไร มีกลยุทธ์หลักสองแบบ สำหรับ State แบบ Global จะประกาศ Ref ไว้นอกฟังก์ชันของ Composable (รูปแบบ Singleton) สำหรับ State เชิงบริบทที่จำกัดอยู่ใน Subtree ของ Component จะใช้ provide/inject กับ InjectionKey ที่มี Type กำกับ ดังที่แสดงในตัวอย่าง useTheme

4. MaybeRefOrGetter คืออะไร และทำไมจึงสำคัญ เป็น Utility Type ของ Vue ที่รับได้ทั้งค่าธรรมดา Ref หรือ Getter Function ช่วยให้ Composables สามารถรับ Input ได้ทั้งแบบ Static และ Reactive ซึ่งเพิ่มความยืดหยุ่นสูงสุดสำหรับผู้ใช้งาน ฟังก์ชัน toValue() จะดึงค่าที่อยู่ภายในออกมาโดยไม่ขึ้นกับประเภทของ Input

5. จะทดสอบ Composables ที่ใช้ onMounted หรือ onUnmounted ได้อย่างไร ใช้ฟังก์ชันช่วยเหลือเช่น withSetup ที่ Mount Component Wrapper ขึ้นมา Component นี้จะเรียกใช้ Composable ภายใน setup() ซึ่งจัดเตรียม Instance Context ที่จำเป็น สำหรับการตรวจสอบการทำความสะอาด จะเรียก unmount() แล้วตรวจสอบว่า Side Effects (Listeners, Timers, Abort Controllers) ถูกกำจัดแล้ว

6. ควรใช้ Pinia แทน Composables กับ provide/inject เมื่อใด Pinia เหมาะสมกว่าเมื่อ State ต้องการเข้าถึงได้จากทุก Component โดยไม่ต้องมีความสัมพันธ์เชิงลำดับชั้น เมื่อต้องการ Persistence (localStorage/sessionStorage) เมื่อต้องการ Integration กับ Vue DevTools สำหรับการ Debug หรือเมื่อ Component Trees หลายชุดต้องการแชร์ State เดียวกัน Composables กับ provide/inject เหมาะกว่าสำหรับ State เชิงบริบทที่จำกัดอยู่ใน Subtree เฉพาะ

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

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

บทสรุป

Composables ของ Vue 3 ไม่ได้เป็นเพียงทางเลือกแทน Mixins แต่เป็นกระบวนทัศน์ในการจัดระเบียบโค้ดที่สามารถขยายได้ตั้งแต่ฟังก์ชันยูทิลิตี้ง่าย ๆ ไปจนถึงระบบที่ซับซ้อนพร้อม Dependency Injection และ Composition หลายระดับ การเชี่ยวชาญรูปแบบเหล่านี้เป็นสิ่งที่แยกนักพัฒนาที่เพียงแค่ใช้ Vue ออกจากนักพัฒนาที่เข้าใจระบบ Reactive อย่างลึกซึ้ง

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

  • โครงสร้างที่ชัดเจน: Typed Interfaces สำหรับ Input และ Output ด้วย UseXxxOptions และ UseXxxReturn
  • การแยก State: แต่ละการเรียกใช้สร้าง Instance ที่เป็นอิสระ ขจัดปัญหาความขัดแย้ง
  • Composition แบบ Explicit: Composables ที่ใช้ Composables อื่นสร้างห่วงโซ่ Reactivity ที่คาดเดาได้
  • การจัดการทรัพยากร: AbortController และ onUnmounted รับประกันการทำความสะอาด Side Effects
  • Injection ที่มี Type กำกับ: provide/inject กับ InjectionKey ทดแทน Prop Drilling โดยไม่สูญเสีย Type Safety
  • ความสามารถในการทดสอบ: รูปแบบ withSetup ช่วยให้ตรวจสอบ Reactivity ในสภาพแวดล้อมที่แยกออกจากกันได้อย่างสมบูรณ์
  • ความเป็นจริง: ตรวจสอบ VueUse ก่อนที่จะสร้าง Composable ที่มีฟังก์ชันการทำงานซ้ำซ้อน

การเตรียมรูปแบบเหล่านี้พร้อมการ Implement จริงและความสามารถในการอธิบายการตัดสินใจด้านการออกแบบเบื้องหลังแต่ละรูปแบบ จะมอบข้อได้เปรียบอย่างมีนัยสำคัญในกระบวนการคัดเลือกเทคนิคสำหรับตำแหน่ง Vue ในปี 2026

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

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

แท็ก

#vue
#composables
#composition-api
#interview
#deep-dive

แชร์

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