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:
- Daha İyi Tip Desteği: TypeScript ile tam uyumlu çalışır ve daha iyi otomatik tamamlama sağlar.
- Logic Reusability: Composable fonksiyonlar sayesinde mantığı kolayca paylaşabilir ve yeniden kullanabilirsiniz.
- Daha İyi Organizasyon: İlgili logic'leri bir arada tutabilir, karmaşık component'leri daha yönetilebilir hale getirebilirsiniz.
- Tree-Shaking: Kullanılmayan özellikleri bundle'dan çıkararak daha küçük bundle boyutları elde edebilirsiniz.
- 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