Vue 3 Composition API ile Modern Component Patterns

Yunus Emre Güzel
8 Ocak 202515 dkVue.js
Vue 3 Composition API ile Modern Component Patterns

Vue 3 Composition API ile Modern Component Patterns

Vue 3'ün en önemli özelliklerinden biri olan Composition API, component mantığını organize etmek ve kod tekrar kullanılabilirliğini artırmak için güçlü bir yol sunar. Options API'nin aksine, Composition API ile logic'leri daha modüler ve fonksiyonel bir şekilde organize edebilir, type safety sağlayabilir ve daha iyi bir developer experience elde edebilirsiniz.

Neden Composition API?

Composition API'nin getirdiği avantajlar:

  1. Daha İyi Tip Desteği: TypeScript ile tam uyumlu çalışır ve daha iyi otomatik tamamlama sağlar.
  2. Logic Reusability: Composable fonksiyonlar sayesinde mantığı kolayca paylaşabilir ve yeniden kullanabilirsiniz.
  3. Daha İyi Organizasyon: İlgili logic'leri bir arada tutabilir, karmaşık component'leri daha yönetilebilir hale getirebilirsiniz.
  4. Tree-Shaking: Kullanılmayan özellikleri bundle'dan çıkararak daha küçük bundle boyutları elde edebilirsiniz.
  5. Daha Az Boilerplate: Özellikle TypeScript ile çalışırken daha az kod yazmanızı sağlar.

Composition API'nin Temel Prensipleri

Composition API, Vue 3 ile birlikte gelen ve component mantığını daha modüler ve yeniden kullanılabilir hale getiren bir yaklaşımdır. Bu yaklaşımın temelinde "composition functions" (composables) ve reaktif referanslar yatar.

Setup Fonksiyonu ve Reaktivite

Setup fonksiyonu, component'in tüm logic'inin tanımlandığı yerdir. Reactive state, computed properties, methods ve lifecycle hooks burada tanımlanır.

import { ref, computed, onMounted, watch } from 'vue'
import type { Ref } from 'vue'

// Tip tanımlamaları ile başlayalım
interface User {
  id: number
  name: string
  email: string
  preferences: {
    theme: 'light' | 'dark'
    notifications: boolean
  }
}

export default defineComponent({
  name: 'UserProfile',
  props: {
    userId: {
      type: Number,
      required: true
    }
  },
  setup(props) {
    // Reactive state tanımlamaları
    // ref() ile primitive değerler için reaktif referanslar oluşturuyoruz
    const user: Ref<User | null> = ref(null)
    const isLoading = ref(false)
    const error = ref<Error | null>(null)

    // Computed properties
    // Reaktif değerlere bağlı hesaplanmış özellikler
    const userFullName = computed(() => {
      if (!user.value) return ''
      return `${user.value.name} (${user.value.email})`
    })

    // Methods
    // Asenkron işlemleri handle eden method
    const fetchUserData = async () => {
      isLoading.value = true // Loading state'ini aktif et
      error.value = null // Önceki hataları temizle
      
      try {
        const response = await fetch(`/api/users/${props.userId}`)
        if (!response.ok) throw new Error('Failed to fetch user data')
        
        user.value = await response.json()
      } catch (e) {
        // Hata yönetimi ve tip kontrolü
        error.value = e instanceof Error ? e : new Error('Unknown error')
      } finally {
        // Her durumda loading state'ini kapat
        isLoading.value = false
      }
    }

    // Lifecycle hooks
    // Component mount olduğunda veriyi çek
    onMounted(() => {
      fetchUserData()
    })

    // Watch effects
    // Props değiştiğinde veriyi güncelle
    watch(() => props.userId, (newId) => {
      fetchUserData()
    })

    // Template'e expose edilecek değerler
    return {
      user,
      isLoading,
      error,
      userFullName
    }
  }
})

Composable Fonksiyonlar: Mantığı İzole Etme ve Yeniden Kullanma

Composable fonksiyonlar, Vue 3'ün en güçlü özelliklerinden biridir. Bu fonksiyonlar sayesinde component logic'lerini izole edebilir, test edilebilir ve yeniden kullanılabilir parçalar haline getirebilirsiniz.

useUser Composable: Kullanıcı Verisi Yönetimi

Bu composable, kullanıcı verisi ile ilgili tüm işlemleri (fetch, update, error handling) tek bir yerde toplar.

// composables/useUser.ts
import { ref, computed } from 'vue'
import type { Ref } from 'vue'

export function useUser(userId: number) {
  // State management
  const user: Ref<User | null> = ref(null)
  const isLoading = ref(false)
  const error = ref<Error | null>(null)

  // Veri çekme işlemi
  const fetchUser = async () => {
    isLoading.value = true
    error.value = null
    
    try {
      const response = await fetch(`/api/users/${userId}`)
      if (!response.ok) throw new Error('Failed to fetch user')
      
      user.value = await response.json()
    } catch (e) {
      error.value = e instanceof Error ? e : new Error('Unknown error')
    } finally {
      isLoading.value = false
    }
  }

  // Kullanıcı güncelleme işlemi
  const updateUser = async (updates: Partial<User>) => {
    if (!user.value) return
    
    try {
      const response = await fetch(`/api/users/${userId}`, {
        method: 'PATCH',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(updates)
      })
      
      if (!response.ok) throw new Error('Failed to update user')
      
      // Başarılı güncelleme sonrası state'i güncelle
      user.value = await response.json()
    } catch (e) {
      error.value = e instanceof Error ? e : new Error('Unknown error')
      throw error.value // Hata yönetimini üst komponente bırak
    }
  }

  // Dışarı expose edilecek değerler ve methodlar
  return {
    user,
    isLoading,
    error,
    fetchUser,
    updateUser
  }
}

// Composable'ın kullanımı
const UserProfile = defineComponent({
  setup() {
    // useUser composable'ını kullan
    const { user, isLoading, error, fetchUser, updateUser } = useUser(1)
    
    // Component mount olduğunda veriyi çek
    onMounted(() => {
      fetchUser()
    })
    
    return {
      user,
      isLoading,
      error,
      updateUser
    }
  }
})

useForm Composable: Form Yönetimi ve Validasyon

Form işlemlerini yönetmek için özelleştirilmiş bir composable. Validasyon, error handling ve form state management'ı tek bir yerde toplar.

// composables/useForm.ts
import { reactive, ref, computed } from 'vue'

// Validasyon kuralı için tip tanımı
interface ValidationRule {
  validate: (value: any) => boolean
  message: string
}

// Form field konfigürasyonu için tip tanımı
interface FieldConfig {
  value: any
  rules?: ValidationRule[]
}

export function useForm<T extends Record<string, any>>(fields: Record<keyof T, FieldConfig>) {
  // Form state'ini reactive olarak oluştur
  const form = reactive(
    Object.keys(fields).reduce((acc, key) => {
      acc[key] = fields[key].value
      return acc
    }, {} as T)
  )

  // Hata state'ini reactive olarak oluştur
  const errors = reactive(
    Object.keys(fields).reduce((acc, key) => {
      acc[key] = []
      return acc
    }, {} as Record<keyof T, string[]>)
  )

  // Form durumu için state'ler
  const isDirty = ref(false)
  const isSubmitting = ref(false)

  // Validasyon fonksiyonu
  const validate = () => {
    let isValid = true
    
    Object.keys(fields).forEach(key => {
      const fieldRules = fields[key].rules || []
      errors[key] = [] // Önceki hataları temizle
      
      // Tüm kuralları kontrol et
      fieldRules.forEach(rule => {
        if (!rule.validate(form[key])) {
          errors[key].push(rule.message)
          isValid = false
        }
      })
    })
    
    return isValid
  }

  // Form geçerliliğini computed property olarak hesapla
  const isValid = computed(() => {
    return Object.values(errors).every(fieldErrors => fieldErrors.length === 0)
  })

  // Formu sıfırlama fonksiyonu
  const resetForm = () => {
    Object.keys(fields).forEach(key => {
      form[key] = fields[key].value
      errors[key] = []
    })
    isDirty.value = false
  }

  return {
    form,
    errors,
    isDirty,
    isSubmitting,
    isValid,
    validate,
    resetForm
  }
}

// useForm composable'ının kullanımı
const UserForm = defineComponent({
  setup() {
    // Form konfigürasyonu ile useForm'u başlat
    const { form, errors, isValid, validate, resetForm } = useForm({
      name: {
        value: '',
        rules: [
          {
            validate: (v: string) => v.length >= 2,
            message: 'İsim en az 2 karakter olmalıdır'
          }
        ]
      },
      email: {
        value: '',
        rules: [
          {
            validate: (v: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v),
            message: 'Geçerli bir email adresi giriniz'
          }
        ]
      }
    })

    // Form submit handler
    const handleSubmit = async () => {
      if (!validate()) return // Validasyon başarısızsa işlemi durdur
      
      try {
        await submitForm(form) // Form verilerini gönder
        resetForm() // Başarılı gönderim sonrası formu sıfırla
      } catch (error) {
        console.error('Form gönderimi başarısız:', error)
      }
    }

    return {
      form,
      errors,
      isValid,
      handleSubmit
    }
  }
})

Performans Optimizasyonları

Vue 3'te performans optimizasyonları için birçok teknik bulunur. İşte en önemli optimizasyon teknikleri:

Computed Properties ve Memorization

Computed properties ve memorization, gereksiz hesaplamaları önlemek için kullanılan önemli tekniklerdir.

import { computed, ref } from 'vue'

// Büyük veri listesi için state
const list = ref([/* büyük veri dizisi */])
const searchQuery = ref('')
const filterType = ref<'all' | 'active' | 'completed'>('all')

// Çoklu bağımlılığı olan optimize edilmiş computed property
const filteredList = computed(() => {
  // İlk filtreleme: Durum bazlı
  const statusFiltered = filterType.value === 'all'
    ? list.value
    : list.value.filter(item => item.status === filterType.value)

  // İkinci filtreleme: Arama bazlı
  return statusFiltered.filter(item =>
    item.title.toLowerCase().includes(searchQuery.value.toLowerCase())
  )
})

// Pahalı hesaplamalar için memorization fonksiyonu
function useMemoize<T extends (...args: any[]) => any>(fn: T) {
  const cache = new Map()
  
  return (...args: Parameters<T>): ReturnType<T> => {
    const key = JSON.stringify(args)
    
    // Cache'de varsa direkt dön
    if (cache.has(key)) {
      return cache.get(key)
    }
    
    // Yoksa hesapla, cache'le ve dön
    const result = fn(...args)
    cache.set(key, result)
    return result
  }
}

// Memorization kullanım örneği
const calculateExpensiveValue = useMemoize((param1: number, param2: number) => {
  // Pahalı hesaplama işlemi
  return /* ... */
})

Async Components ve Dynamic Imports

Büyük component'leri lazy loading ile yükleyerek initial bundle size'ı küçültebilirsiniz.

// Basit lazy loading
const HeavyComponent = defineAsyncComponent(() =>
  import('./components/HeavyComponent.vue')
)

// Gelişmiş konfigürasyon ile lazy loading
const HeavyComponentWithOptions = defineAsyncComponent({
  // Component loader
  loader: () => import('./components/HeavyComponent.vue'),
  
  // Loading durumunda gösterilecek component
  loadingComponent: LoadingSpinner,
  
  // Hata durumunda gösterilecek component
  errorComponent: ErrorDisplay,
  
  // Loading component'in gösterilmesi için minimum gecikme
  delay: 200,
  
  // Maximum yükleme süresi
  timeout: 3000,
  
  // Hata durumunda retry yapılıp yapılmayacağı
  retry: 3
})

Component Composition Patterns

Modern Vue uygulamalarında kullanabileceğiniz ileri seviye component pattern'leri:

Higher-Order Components (HOC)

HOC'lar, component'lere ek özellikler eklemek için kullanılan bir pattern'dir.

// components/withLoading.ts
export function withLoading(WrappedComponent: Component) {
  return defineComponent({
    name: 'WithLoading',
    props: {
      isLoading: {
        type: Boolean,
        default: false
      }
    },
    setup(props, { slots }) {
      return () => {
        // Loading durumunda loading spinner göster
        if (props.isLoading) {
          return h('div', { class: 'loading-container' }, [
            h(LoadingSpinner)
          ])
        }
        
        // Normal durumda wrapped component'i render et
        return h(WrappedComponent, {}, slots)
      }
    }
  })
}

// HOC kullanımı
const UserListWithLoading = withLoading(UserList)

Renderless Components

Logic'i UI'dan ayırmak için kullanılan bir pattern'dir. Tüm logic'i component içinde tutar ama render işlemini parent'a bırakır.

// components/DataFetcher.vue
<script setup lang="ts">
import { ref, onMounted } from 'vue'

// Props tipini tanımla
const props = defineProps<{
  url: string
}>()

// State management
const data = ref(null)
const isLoading = ref(false)
const error = ref(null)

// Veri çekme fonksiyonu
async function fetchData() {
  isLoading.value = true
  error.value = null
  
  try {
    const response = await fetch(props.url)
    data.value = await response.json()
  } catch (e) {
    error.value = e
  } finally {
    isLoading.value = false
  }
}

// Component mount olduğunda veriyi çek
onMounted(fetchData)
</script>

<template>
  <!-- Tüm state'leri ve methodları slot'a geçir -->
  <slot
    :data="data"
    :isLoading="isLoading"
    :error="error"
    :refetch="fetchData"
  />
</template>

<!-- Renderless component kullanımı -->
<DataFetcher url="/api/users">
  <template #default="{ data, isLoading, error, refetch }">
    <div v-if="isLoading">Yükleniyor...</div>
    <div v-else-if="error">Hata: {{ error.message }}</div>
    <div v-else>
      <UserList :users="data" />
      <button @click="refetch">Yenile</button>
    </div>
  </template>
</DataFetcher>

Best Practices ve İpuçları

Vue 3 ve Composition API ile çalışırken dikkat edilmesi gereken önemli noktalar:

1. State Management

  • Küçük-Orta Ölçekli Uygulamalar:

    • Composables kullanın
    • Provide/Inject pattern'ini değerlendirin
    • Basit state management için reactive() ve ref() yeterli olabilir
  • Büyük Ölçekli Uygulamalar:

    • Pinia kullanın
    • Modüler store yapısı oluşturun
    • State'i kategorilere ayırın
  • Genel İlkeler:

    • State'i mümkün olduğunca lokal tutun
    • Global state'i minimize edin
    • State değişimlerini izlenebilir kılın

2. Code Organization

  • Composables:

    • Mantıksal gruplar halinde organize edin
    • Her composable tek bir sorumluluğa sahip olsun
    • İyi dökümante edin
  • TypeScript:

    • Interface ve type'ları ayrı dosyalarda tutun
    • Generic type'ları etkin kullanın
    • Strict mode'u aktif tutun
  • Dosya Yapısı:

    src/
    ├── composables/
    │   ├── useUser.ts
    │   ├── useForm.ts
    │   └── useAuth.ts
    ├── components/
    │   ├── base/
    │   └── features/
    ├── types/
    │   └── index.ts
    └── utils/
        └── helpers.ts
    

3. Performance

  • Computed Properties:

    • Karmaşık hesaplamalar için computed kullanın
    • Gereksiz hesaplamaları önleyin
    • Bağımlılıkları minimize edin
  • Render Optimizasyonu:

    • v-show vs v-if kullanımına dikkat edin
    • Gereksiz re-render'ları önleyin
    • Key attribute'unu doğru kullanın
  • Lazy Loading:

    • Route-based code splitting yapın
    • Büyük component'leri lazy load edin
    • Prefetching stratejileri belirleyin

4. Error Handling

  • Error Boundaries:

    • Hataları uygun seviyede yakalayın
    • Kullanıcı dostu hata mesajları gösterin
    • Development vs Production hata yönetimini ayırın
  • Logging:

    • Hataları merkezi bir yerde toplayın
    • Error tracking servisleri kullanın
    • Debug bilgilerini environment'a göre ayarlayın

Sonuç

Composition API, Vue 3 uygulamalarında mantık organizasyonu ve kod tekrar kullanılabilirliği için güçlü bir araç sunar. Bu pattern'leri kullanarak:

  • Daha Maintainable Kod:

    • Modüler yapı
    • Kolay test edilebilirlik
    • İyi organize edilmiş codebase
  • Daha İyi Performans:

    • Optimize edilmiş render döngüsü
    • Daha küçük bundle size
    • Daha hızlı initial load
  • Daha İyi Developer Experience:

    • TypeScript desteği
    • IDE integration
    • Daha az boilerplate kod
  • Daha Modüler ve Test Edilebilir Kod:

    • İzole edilmiş logic'ler
    • Bağımsız test edilebilirlik
    • Kolay refactoring

elde edebilirsiniz.

İlgili Etiketler: #Vue3 #CompositionAPI #TypeScript #Performance #ComponentDesign #WebDevelopment

Kaynaklar