11.02.2026

Vue 3 ile Composition API’de “Composable” Tasarlama: Tekrarlanan Mantığı Pakete Dönüştür

Composition API’de composable yazarak tekrar eden UI/iş mantığını temiz, test edilebilir ve yeniden kullanılabilir hale getirin.

Neden “composable”?

Vue 3 Composition API ile aynı sayfada tekrar tekrar yazdığımız mantıklar (arama, debounce, localStorage senkronu, kısayol tuşları, online/offline takibi) hızla dağınık hale gelir. Composable, bu mantığı küçük bir fonksiyona taşıyıp farklı bileşenlerde tekrar kullanmanızı sağlar.

Bu yazıda iki pratik örnekle “composable tasarlama” yaklaşımını göstereceğim:

  1. Debounced arama + URL query ile senkron
  2. localStorage ile kalıcı state (reactive)

Örnekler Vue 3 + <script setup> içindir.


1) useDebouncedRef: Gecikmeli (debounced) ref

Kullanıcı yazdıkça API çağırmak yerine, yazmayı bıraktığında tetiklemek istersiniz.

// composables/useDebouncedRef.js
import { ref, watch } from 'vue'

export function useDebouncedRef(initialValue = '', delay = 300) {
  const state = ref(initialValue)
  const debounced = ref(initialValue)
  let timer

  watch(state, (val) => {
    clearTimeout(timer)
    timer = setTimeout(() => {
      debounced.value = val
    }, delay)
  }, { flush: 'sync' })

  return { state, debounced }
}

Kullanım

<script setup>
import { computed, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useDebouncedRef } from '@/composables/useDebouncedRef'

const route = useRoute()
const router = useRouter()

const { state: query, debounced: debouncedQuery } = useDebouncedRef(route.query.q ?? '', 400)

// URL query -> input (route değişirse)
watch(() => route.query.q, (q) => {
  if ((q ?? '') !== query.value) query.value = q ?? ''
})

// input -> URL query (debounced)
watch(debouncedQuery, (q) => {
  router.replace({ query: { ...route.query, q: q || undefined } })
})

const hint = computed(() => debouncedQuery.value
  ? `Aranıyor: “${debouncedQuery.value}”`
  : 'Arama için yazın')
</script>

<template>
  <input v-model="query" placeholder="Ürün ara..." />
  <p>{{ hint }}</p>
</template>

Kazanç:

  • Bileşen sade kalır
  • Debounce davranışı standartlaşır
  • URL senkronu ile paylaşılabilir linkler oluşur

2) useStorage: localStorage ile kalıcı reactive state

Tema seçimi, son kullanılan filtreler veya basit taslaklar için ideal.

// composables/useStorage.js
import { ref, watch } from 'vue'

export function useStorage(key, defaultValue) {
  const read = () => {
    try {
      const raw = localStorage.getItem(key)
      return raw == null ? defaultValue : JSON.parse(raw)
    } catch {
      return defaultValue
    }
  }

  const state = ref(read())

  watch(state, (val) => {
    localStorage.setItem(key, JSON.stringify(val))
  }, { deep: true })

  // Diğer sekmelerle senkron
  window.addEventListener('storage', (e) => {
    if (e.key === key) state.value = e.newValue ? JSON.parse(e.newValue) : defaultValue
  })

  return state
}

Kullanım: “Okuma Modu” anahtarı

<script setup>
import { computed } from 'vue'
import { useStorage } from '@/composables/useStorage'

const readingMode = useStorage('reading_mode', false)
const className = computed(() => readingMode.value ? 'reading' : 'normal')
</script>

<template>
  <section :class="className">
    <label>
      <input type="checkbox" v-model="readingMode" />
      Okuma modu
    </label>
    <p>Bu tercih sayfayı yenileseniz de kalır.</p>
  </section>
</template>

<style>
.reading { max-width: 720px; line-height: 1.8; font-size: 18px; }
.normal { max-width: 980px; line-height: 1.5; }
</style>


Composable yazarken 4 küçük kural

  1. Tek sorumluluk: useDebouncedRef sadece debounce bilsin.
  2. İsimlendirme: useX kalıbı + döndürülen değerler net olsun.
  3. Yan etkileri kontrol et: event listener/interval varsa onBeforeUnmount ile temizle.
  4. Test edilebilirlik: mümkünse dış bağımlılıkları parametre yap (delay, storage key, vb.).

Sonuç

Composable yaklaşımıyla Vue bileşenleri “sayfa kodu” olmaktan çıkıp, küçük ve okunabilir UI parçalarına dönüşür. Debounce, URL senkronu, localStorage gibi tekrar eden ihtiyaçları bir kez çözüp her yerde kullanabilirsiniz.

İstersen bir sonraki adım olarak useClipboard, useOnlineStatus veya useHotkeys gibi composable’lar da tasarlayabiliriz—hepsi aynı mimariyle ilerler.