React'te Gelişmiş Komponent Desenleri: Kontrol Props, Prop Getters ve State Reducers ile Esnek ve Yeniden Kullanılabilir Uygulamalar

React, bildiğiniz gibi modern kullanıcı arayüzleri oluşturmak için vazgeçilmez bir kütüphane. Komponent tabanlı mimarisi, kodumuzu modüler parçalara ayırmamızı ve bu parçaları tekrar tekrar kullanmamızı sağlıyor. Ancak projelerimiz büyüdükçe ve komponentlerimiz daha karmaşık etkileşimler içermeye başladıkça, sadece temel prop geçişi veya basit state yönetimiyle ilerlemek yetersiz kalabiliyor. İşte bu noktada, komponentlerimizin davranışlarını ve render mekanizmalarını daha dışarıdan kontrol edilebilir, özelleştirilebilir ve test edilebilir hale getiren gelişmiş komponent desenleri devreye giriyor.
Benim geliştirme tecrübelerimde, özellikle büyük ölçekli ve ekip içerisinde standartlaşmanın önemli olduğu React projelerinde, bu tür desenleri uygulamak, boilerplate kodu azaltmanın, komponentleri daha esnek kılmanın ve geliştirme hızını artırmanın anahtarı oldu. Bu yazıda, React komponentlerinizin potansiyelini bir üst seviyeye taşıyacak, belki de ilk bakışta karmaşık gelebilecek ama temel prensipleri anlaşıldığında uygulamanıza büyük değer katacak Control Props, Prop Getters ve State Reducers gibi desenleri derinlemesine inceleyeceğiz. Her bir deseni pratik örneklerle açıklayarak, kendi projelerinizde nasıl uygulayabileceğinizi göstereceğim.
Neden Gelişmiş Komponent Desenlerine İhtiyaç Duyarız?
Bir komponenti geliştirdiğimizde, onun belirli bir senaryo için en iyi şekilde çalıştığından emin olmak isteriz. Ancak bu komponenti farklı yerlerde, farklı ihtiyaçlarla kullanmak istediğimizde, esnekliğinin sınırlarına takılırız. Örneğin, bir `Button` komponenti kolayca yeniden kullanılabilirken, daha karmaşık bir `Toggle` (açma/kapama) veya `Accordion` (akordeon) komponentinin state'ini ve davranışını dışarıdan kontrol etme ihtiyacı doğabilir. Geleneksel prop drilling ve callback yapısı, bu karmaşıklığı yönetmekte zorlanabilir.
Gelişmiş komponent desenleri, bu tür zorlukları aşmak için tasarlanmıştır:
- Yeniden Kullanılabilirlik: Komponentleri farklı senaryolara uyarlayarak daha geniş bir kullanım alanı sunar.
- Esneklik: Komponentin iç state'ini ve davranışını dışarıdan manipüle etme imkanı verir.
- İzolasyon: Komponentin iç mantığını dış dünyaya açmadan özelleştirme sağlar.
- Test Edilebilirlik: Davranışları daha öngörülebilir hale getirerek test yazmayı kolaylaştırır.

1. Control Props Deseni: Dışarıdan Kontrol Edilebilen Komponentler
Control Props deseni, bir komponentin kendi iç state'ini yönetebilmesi (uncontrolled) veya bu state'in tamamen ebeveyn komponent tarafından yönetilmesi (controlled) arasında bir esneklik sağlamasına olanak tanır. HTML'deki <input type="text" value="" onChange=""> örneği bunun en bilinen halidir. Eğer value prop'u sağlanırsa, input controlled olur; sağlanmazsa uncontrolled kalır.
Amaç: Komponentin iç state'ini (örneğin bir `Toggle` komponentinin `isOn` state'ini) ebeveyn komponentin kontrolüne bırakmak.
Kullanım Alanları: Form elemanları, modallar, akordeonlar, sliderlar gibi hem kendi kendine çalışabilen hem de dışarıdan tam kontrol gerektiren interaktif UI elemanları.
Örnek: Kontrol Edilebilir Bir Toggle Komponenti
import React, { useState } from 'react';
function Toggle({ isOn: controlledIsOn, onToggle: controlledOnToggle, defaultIsOn = false }) {
const [internalIsOn, setInternalIsOn] = useState(defaultIsOn);
// isControlled true ise dışarıdan kontrol ediliyor demektir.
const isControlled = controlledIsOn !== undefined;
const isOn = isControlled ? controlledIsOn : internalIsOn;
const handleToggle = () => {
if (isControlled) {
// Dışarıdan kontrol ediliyorsa, sadece dışarıdaki onToggle'ı çağır
controlledOnToggle(!isOn);
} else {
// Kendi state'ini yönetiyorsa, kendi state'ini güncelle ve onToggle'ı çağır (varsa)
setInternalIsOn(!isOn);
controlledOnToggle?.(!isOn);
}
};
return (
<button onClick={handleToggle} style={{ backgroundColor: isOn ? 'green' : 'gray', color: 'white', padding: '10px' }}>
{isOn ? 'Açık' : 'Kapalı'}
</button>
);
}
// Kullanım Senaryosu 1: Uncontrolled (kendi state'ini yöneten)
function AppUncontrolled() {
return (
<div>
<h3>Uncontrolled Toggle</h3>
<Toggle defaultIsOn={true} onToggle={(newState) => console.log('Toggle State:', newState)} />
</div>
);
}
// Kullanım Senaryosu 2: Controlled (dışarıdan state'i yönetilen)
function AppControlled() {
const [appIsOn, setAppIsOn] = useState(false);
const handleAppToggle = (newState) => {
console.log('App Controlled Toggle State:', newState);
setAppIsOn(newState);
};
return (
<div>
<h3>Controlled Toggle</h3>
<Toggle isOn={appIsOn} onToggle={handleAppToggle} />
<p>Uygulama State: {appIsOn ? 'Açık' : 'Kapalı'}</p>
</div>
);
}
// Her iki senaryoyu birleştiren ana uygulama
function RootApp() {
return (
<div>
<AppUncontrolled />
<hr/>
<AppControlled />
</div>
)
}
export default RootApp;
Bu örnekte `Toggle` komponenti, isOn prop'u verildiğinde controlled, verilmediğinde ise defaultIsOn prop'u ile kendi state'ini yöneten (uncontrolled) bir yapıya bürünüyor. Bu, komponentin hem basit kullanım senaryolarında kolayca kullanılabilmesini hem de daha karmaşık, dışarıdan state senkronizasyonu gerektiren durumlarda esneklik sunmasını sağlar.
2. Prop Getters Deseni: İç State'i Açığa Çıkarmak
Prop Getters deseni, bir komponentin iç state'ini ve olay yöneticilerini (event handlers) dış dünyaya, belirli fonksiyonlar aracılığıyla kontrollü bir şekilde açığa çıkarmasını sağlar. Bu desen, genellikle bir komponentin state'ini yönettiği ancak bu state'in belirli durumlarda dışarıdan da özelleştirilmesi gereken durumlarda kullanılır. Özellikle Custom Hooks ile birleştiğinde gücü artar.
Amaç: Komponentin temel işlevselliğini korurken, render edeceği prop'ları ve event handler'ları ebeveyn komponentin dinamik olarak belirlemesine izin vermek.
Kullanım Alanları: Yeniden kullanılabilir UI kütüphaneleri, kompleks form elemanları, drag-and-drop bileşenleri gibi UI mantığını soyutlayıp, UI'ın render edilişini kullanıcının kontrolüne bırakmak istediğimiz durumlar.
Örnek: Prop Getters ile Esnek Bir Açılır Menü (Dropdown)
import React, { useState, useRef, useEffect } from 'react';
// Custom Hook: useDropdownState - Açılır menünün state mantığını yönetir
function useDropdownState({ initialOpen = false } = {}) {
const [isOpen, setIsOpen] = useState(initialOpen);
const toggle = () => setIsOpen(prev => !prev);
const close = () => setIsOpen(false);
const open = () => setIsOpen(true);
function getToggleButtonProps(props = {}) {
return {
'aria-expanded': isOpen,
'aria-haspopup': 'true',
onClick: toggle,
...props,
};
}
function getMenuProps(props = {}) {
return {
style: { display: isOpen ? 'block' : 'none' },
...props,
};
}
return { isOpen, toggle, open, close, getToggleButtonProps, getMenuProps };
}
// Dropdown komponenti - UI'ı render eder
function Dropdown({ children }) {
const { isOpen, getToggleButtonProps, getMenuProps } = useDropdownState();
return (
<div style={{ position: 'relative', display: 'inline-block' }}>
<button {...getToggleButtonProps()}>
{isOpen ? 'Kapat' : 'Aç'}
</button>
<div {...getMenuProps()} style={{ position: 'absolute', backgroundColor: '#f9f9f9', minWidth: '160px', boxShadow: '0px 8px 16px 0px rgba(0,0,0,0.2)', zIndex: 1, ...getMenuProps().style }}>
{children}
</div>
</div>
);
}
// Dropdown'ı kullanan uygulama
function AppWithDropdown() {
return (
<div>
<h3>Prop Getters ile Dropdown</h3>
<Dropdown>
<a href="#" style={{ padding: '12px 16px', display: 'block', textDecoration: 'none', color: 'black' }}>Seçenek 1</a>
<a href="#" style={{ padding: '12px 16px', display: 'block', textDecoration: 'none', color: 'black' }}>Seçenek 2</a>
<a href="#" style={{ padding: '12px 16px', display: 'block', textDecoration: 'none', color: 'black' }}>Seçenek 3</a>
</Dropdown>
</div>
);
}
export default AppWithDropdown;
useDropdownState hook'u, açılır menünün state mantığını (açık/kapalı olma durumu) ve bu state'i manipüle eden fonksiyonları (`toggle`, `open`, `close`) soyutlar. `getToggleButtonProps` ve `getMenuProps` fonksiyonları, dışarıdan çağrıldığında, ilgili DOM elemanlarına uygulanması gereken prop'ları (olay yöneticileri, erişilebilirlik nitelikleri, stil vb.) döndürür. Bu sayede, `Dropdown` komponenti, iç state mantığını korurken, UI'ın nasıl görüneceği ve hangi prop'ları alacağı konusunda esneklik sunar.

3. State Reducers Deseni: State Geçişlerini Özelleştirmek
State Reducers deseni, bir komponentin kendi iç state yönetimini dışarıya açarak, ebeveyn komponentin veya kullanıcı kodunun, komponentin state geçişlerini (yani bir olay sonrası state'in nasıl değişeceğini) kontrol etmesine olanak tanır. Bu, özellikle karmaşık state makineleri içeren komponentlerde inanılmaz bir esneklik sağlar. Hatırlarsanız, React Uygulamalarında State Makineleri yazımda da kompleks state geçişlerinin öneminden bahsetmiştim.
Amaç: Komponentin dahili state güncelleme mantığını değiştirmek veya üzerine eklemeler yapmak için bir `reducer` fonksiyonu sağlamak.
Kullanım Alanları: Otomatik tamamlama (autocomplete), sliderlar, takvimler veya herhangi bir kompleks, çok adımlı state yönetimi gerektiren UI komponentleri.
Örnek: State Reducer ile Kontrol Edilebilen Bir Sayaç (Counter)
import React, { useReducer } from 'react';
// Varsayılan reducer fonksiyonu
const defaultCountReducer = (state, action) => {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error(`Bilinmeyen aksiyon tipi: ${action.type}`);
}
};
// useCounter custom Hook'u
function useCounter({ initialCount = 0, reducer = defaultCountReducer } = {}) {
const [state, dispatch] = useReducer(reducer, { count: initialCount });
const increment = () => dispatch({ type: 'increment' });
const decrement = () => dispatch({ type: 'decrement' });
return { count: state.count, increment, decrement };
}
// Counter komponenti
function Counter({ ...props }) {
const { count, increment, decrement } = useCounter(props);
return (
<div>
<p>Sayaç: <strong>{count}</strong></p>
<button onClick={increment} style={{ marginRight: '10px' }}>Artır</button>
<button onClick={decrement}>Azalt</button>
</div>
);
}
// Custom Reducer ile Kullanım Senaryosu: Minimum değeri olan sayaç
const minCountReducer = (state, action) => {
if (action.type === 'decrement' && state.count === 0) {
return state; // 0'ın altına düşürme
}
return defaultCountReducer(state, action);
};
function AppWithStateReducer() {
return (
<div>
<h3>Varsayılan Sayaç</h3>
<Counter initialCount={5} />
<h3>Minimum Değerli Sayaç (State Reducer ile)</h3>
<Counter initialCount={2} reducer={minCountReducer} />
</div>
);
}
export default AppWithStateReducer;
Bu örnekte `useCounter` hook'u, reducer prop'u aracılığıyla dışarıdan bir reducer fonksiyonu alabilir. Bu, Counter komponentinin temel artırma/azaltma mantığını korurken, state'in nasıl güncelleneceğine dair kuralları (örneğin, 0'ın altına düşmeme gibi) dışarıdan özelleştirmemizi sağlar. Bu desen, komponentlerinizi daha modüler ve iş mantığına göre adapte edilebilir kılar, tıpkı Tasarım Desenleri yazımda bahsettiğim esneklik prensibi gibi.
Gelişmiş Komponent Desenlerini Ne Zaman Kullanmalıyız?
Bu desenler güçlüdür, ancak her zaman kullanılması gerekmez. Basit komponentler için aşırı mühendislikten kaçınmak önemlidir. Peki, ne zaman bu desenlere yönelmeliyiz?
- Yeniden Kullanım İhtiyacı: Eğer aynı UI ve davranış mantığına sahip bir komponenti, farklı state yönetimi veya etkileşim kurallarıyla birden fazla yerde kullanmanız gerekiyorsa.
- Esneklik Talebi: Komponentinizin state'inin veya davranışının kullanıcılar (diğer geliştiriciler) tarafından kolayca özelleştirilebilmesini istiyorsanız.
- Kütüphane Geliştirme: Eğer bir UI kütüphanesi oluşturuyorsanız, bu desenler kütüphanenizin kullanıcılarına maksimum kontrol ve esneklik sunmanızı sağlar.
- Karmaşık State Mantığı: Bir komponentin birden fazla state geçişi ve karmaşık etkileşimleri varsa, State Reducers deseni bu mantığı daha düzenli hale getirebilir.
Unutmayın, bu desenlerin amacı kodu karmaşıklaştırmak değil, doğru soyutlama seviyesini sağlayarak daha sürdürülebilir ve yönetilebilir uygulamalar inşa etmektir. Başlangıçta öğrenme eğrisi olsa da, uzun vadede projenizin sağlığı için vazgeçilmez hale gelebilirler.
Sonuç
React ekosistemi, sadece temel komponent ve prop kullanımından çok daha fazlasını sunar. Control Props, Prop Getters ve State Reducers gibi gelişmiş komponent desenleri, komponentlerimizi sadece görsel elemanlar olmaktan çıkarıp, daha dinamik, esnek ve yeniden kullanılabilir
Yorumlar
Yorum Gönder