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