React Values ile Kolay Durum Yönetimi: Valtio Örneğiyle Modern Yaklaşımlar
React uygulamaları geliştikçe, bileşenler arası veri akışını ve uygulamanın genel durumunu yönetmek karmaşık bir hal alabilir. Durum yönetimi (state management), bir React uygulamasının kalbinde yer alan ve uygulamanın ne zaman, nerede ve nasıl güncelleneceğini belirleyen kritik bir konudur. Yıllar içinde bu alanda birçok farklı yaklaşım ortaya çıkmıştır: useState ve useReducer gibi temel React hook’ları, Context API gibi yerleşik çözümler ve Redux, MobX, Zustand gibi üçüncü taraf kütüphaneler. Her birinin kendine göre avantajları ve dezavantajları bulunsa da, çoğu zaman geliştiricileri “fazla mı karmaşık?” veya “yeterince esnek mi?” gibi sorularla baş başa bırakmıştır.
Bu makalede, React ekosisteminde son zamanlarda popülerlik kazanan ve “React Values” olarak adlandırabileceğimiz, doğrudan JavaScript nesnelerini reaktif hale getirme felsefesine dayanan yeni nesil bir durum yönetimi yaklaşımını inceleyeceğiz. Özellikle, bu yaklaşımın önde gelen temsilcilerinden biri olan valtio kütüphanesi üzerinden, bu metodolojinin nasıl çalıştığını, geleneksel yöntemlere kıyasla sunduğu avantajları ve hangi senaryolarda tercih edilmesi gerektiğini detaylı bir şekilde ele alacağız. Amacımız, geliştiricilere daha basit, daha sezgisel ve daha performanslı bir durum yönetimi deneyimi sunan bu modern yaklaşımı tanıtmak ve React uygulamalarınızda durum yönetimini kolaylaştırmanın yollarını göstermektir.
Geleneksel Yaklaşımların Ortaya Çıkardığı Sorunlar
React’in durum yönetimi evrimi, geliştiricilerin karşılaştığı sorunlara çözüm bulma arayışının bir sonucudur. Her ne kadar temel yaklaşımlar belirli senaryolar için yeterli olsa da, uygulamanın boyutu ve karmaşıklığı arttıkça bazı kısıtlamalar ortaya çıkar.
useState ve useReducer ile Prop Drilling
useState ve useReducer, React bileşenlerinin kendi iç durumlarını yönetmek için harika araçlardır. Ancak, bir durumu birden fazla bileşen arasında paylaşmak gerektiğinde, özellikle de bu bileşenler bileşen ağacında birbirinden uzak olduğunda, “prop drilling” (prop delme) adı verilen bir sorunla karşılaşılır. Prop drilling, bir veriyi, o veriyi doğrudan kullanmayan ara bileşenler üzerinden geçirme zorunluluğudur. Bu durum, kodun okunabilirliğini azaltır, bakımı zorlaştırır ve bileşenlerin yeniden kullanılabilirliğini olumsuz etkiler. Her ne kadar useContext hook’u bu soruna bir çözüm sunsa da, kendi içinde başka zorlukları beraberinde getirir.
Context API ve Gereksiz Yeniden Oluşturmalar
React’in Context API‘si, prop drilling sorununu çözmek için tasarlanmış güçlü bir araçtır. Bir bağlam sağlayıcısı (Context Provider) aracılığıyla tüm alt bileşenlere veri aktarımını kolaylaştırır. Ancak Context API‘nin önemli bir dezavantajı vardır: Bir Context içindeki değer değiştiğinde, o Context‘i tüketen tüm bileşenler, o değerin sadece bir kısmını kullanıyor olsalar bile yeniden oluşturulur (re-render). Bu, özellikle büyük ve sık güncellenen bağlamlarda performans sorunlarına yol açabilir. React.memo veya useMemo gibi optimizasyon teknikleri kullanılsa bile, bu durum Context API‘nin doğasında var olan bir zorluktur ve karmaşık durumlar için her zaman en verimli çözüm olmayabilir.
Redux ve MobX Gibi Harici Kütüphanelerin Karmaşıklığı
Redux ve MobX gibi üçüncü taraf durum yönetimi kütüphaneleri, büyük ölçekli ve karmaşık uygulamalar için güçlü çözümler sunar. Redux, tek bir immutable durum ağacı, eylemler (actions), azaltıcılar (reducers) ve seçiciler (selectors) gibi katı bir mimari ile tutarlı ve tahmin edilebilir bir durum yönetimi sağlar. MobX ise daha esnek, gözlemlenebilir (observable) veriler ve reaktif programlama prensiplerine dayanır.
Ancak, bu kütüphanelerin her ikisi de özellikle küçük ve orta ölçekli uygulamalar için aşırıya kaçan bir öğrenme eğrisi ve “boilerplate” (kalıp kod) gerektirebilir. Redux’ta bir durumu güncellemek için eylem tanımlamaları, azaltıcılar, dispatch fonksiyonları ve seçiciler yazmak gerekir. MobX daha az kalıp kod gerektirse de, gözlemleyiciler, eylemler ve tepkiler gibi kendine özgü kavramları vardır. Bu karmaşıklık, geliştirme hızını düşürebilir ve özellikle yeni başlayanlar için göz korkutucu olabilir. Uygulamanın ihtiyacından daha karmaşık bir çözüm kullanmak, gereksiz yük ve bakım maliyeti anlamına gelir.
Bu sorunlar, geliştiricileri daha basit, daha sezgisel ve daha az kalıp kod gerektiren, ancak aynı zamanda performanslı ve esnek yeni nesil durum yönetimi yaklaşımlarına yöneltmiştir. İşte tam da bu noktada, “React Values” felsefesini benimseyen kütüphaneler devreye girer.
React Values Felsefesini Tanıyalım: Valtio Örneği
“React Values” terimi, React ekosisteminde belirli bir API’yi değil, daha çok belirli bir durumu yönetme felsefesini ifade eder: Düz JavaScript nesnelerini (plain JavaScript objects) durum olarak kullanmak ve bu nesneleri reaktif hale getirerek, yalnızca ilgili kısımları değiştiğinde bileşenlerin yeniden oluşturulmasını sağlamak. Bu felsefenin en başarılı ve popüler uygulamalarından biri de valtio kütüphanesidir.
valtio, durum yönetimini basitleştirmek için JavaScript’in Proxy nesnesinden yararlanır. Temel amacı, geliştiricilerin durumlarını doğrudan JavaScript nesneleri gibi manipüle etmelerine olanak tanırken, arka planda React’in yeniden oluşturma mekanizmasını akıllıca optimize etmektir.
Valtio’nun Temel Felsefesi
valtio‘nun temel felsefesi oldukça basittir:
1. Düz JavaScript Nesneleri: Durumunuzu karmaşık sınıflar, eylemler veya azaltıcılar yerine, bildiğiniz ve sevdiğiniz düz JavaScript nesneleri olarak tanımlayın.
2. Doğrudan Mutasyon: Durumu güncellemek için setState veya dispatch gibi özel fonksiyonlara ihtiyacınız yok. Durum nesnesinin özelliklerini doğrudan değiştirin (mutate edin).
3. Otomatik Reaktivite: valtio, Proxy nesneleri sayesinde durum nesnesindeki değişiklikleri otomatik olarak algılar ve yalnızca ilgili bileşenlerin yeniden oluşturulmasını tetikler. Bu, Context API‘nin aksine, gereksiz yeniden oluşturmaları en aza indirir.
4. Minimal API: Öğrenilecek çok az API vardır, bu da geliştirme sürecini hızlandırır ve kütüphaneyi yeni başlayanlar için bile erişilebilir kılar.
Bu yaklaşım, geliştiricilerin daha az kod yazmasına, daha az soyutlama ile uğraşmasına ve daha çok iş mantığına odaklanmasına olanak tanır.
Valtio ile Durum Yönetimi Nasıl Çalışır?
Şimdi, valtio‘nun temel özelliklerini ve bir React uygulamasında nasıl kullanıldığını adım adım inceleyelim.
1. Durum (Store) Oluşturma
valtio ile bir durum oluşturmak son derece basittir. Tek yapmanız gereken, durumunuzu temsil eden bir JavaScript nesnesini proxy fonksiyonuna iletmektir:
// store.js
import { proxy } from 'valtio';
const appState = proxy({
count: 0,
text: 'Merhaba Valtio!',
user: {
name: 'John Doe',
age: 30,
},
items: [],
});
export default appState;
Burada appState, uygulamanızın global durumunu temsil eden bir proxy nesnesidir. Bu nesneyi istediğiniz kadar karmaşık yapabilirsiniz, iç içe nesneler ve diziler içerebilir.
2. Durumu Güncelleme
Durumu güncellemek, valtio‘nun en cazip özelliklerinden biridir. Redux’taki gibi eylem tanımları veya azaltıcılar yazmanıza gerek yoktur. Durum nesnesinin özelliklerini doğrudan değiştirerek (mutate ederek) güncelleyebilirsiniz:
// store.js (devam)
export const increment = () => {
appState.count++;
};
export const decrement = () => {
appState.count--;
};
export const updateText = (newText) => {
appState.text = newText;
};
export const updateUserAge = (newAge) => {
appState.user.age = newAge; // İç içe nesneleri doğrudan güncelleyebilirsiniz
};
export const addItem = (item) => {
appState.items.push(item); // Dizilere doğrudan eleman ekleyebilirsiniz
};
Bu kadar basit! Durum nesnesi üzerindeki herhangi bir doğrudan değişiklik, valtio tarafından algılanır.
3. Bileşenlerde Durumu Tüketme
Durumu bir React bileşeninde kullanmak için useSnapshot hook’unu kullanırız. Bu hook, proxy nesnesinin bir anlık görüntüsünü (snapshot) alır ve yalnızca erişilen özellikler değiştiğinde bileşenin yeniden oluşturulmasını tetikler.
// Counter.jsx
import React from 'react';
import { useSnapshot } from 'valtio';
import appState, { increment, decrement } from './store';
function Counter() {
const snap = useSnapshot(appState); // appState'in anlık görüntüsünü al
console.log('Counter bileşeni yeniden oluşturuldu.');
return (
Sayaç: {snap.count}
);
}
export default Counter;
// UserInfo.jsx
import React from 'react';
import { useSnapshot } from 'valtio';
import appState, { updateUserAge } from './store';
function UserInfo() {
const snap = useSnapshot(appState);
console.log('UserInfo bileşeni yeniden oluşturuldu.');
return (
Kullanıcı Bilgileri
Ad: {snap.user.name}
Yaş: {snap.user.age}
);
}
export default UserInfo;
Önemli Not: useSnapshot tarafından döndürülen snap nesnesi immutable’dır (değiştirilemez). Bu, React’in doğal veri akışına uyum sağlar ve beklenmedik mutasyonları önler. Durumu güncellemek için her zaman orijinal appState proxy nesnesini kullanmalısınız.
useSnapshot‘ın güzelliği, snap.count‘a eriştiğinizde, valtio‘nun sadece count özelliğindeki değişiklikleri dinlemesidir. Eğer appState.text değişirse, Counter bileşeni yeniden oluşturulmaz, çünkü Counter bileşeni snap.text‘e erişmemektedir. Bu, Context API‘nin aksine çok daha ince taneli (fine-grained) bir reaktivite sağlar.
4. Türetilmiş Durum (Derived State)
Bazen mevcut durumdan türetilmiş (computed) değerlere ihtiyacımız olur. valtio‘da bunu getter’lar veya basit fonksiyonlar aracılığıyla yapabiliriz:
// store.js (devam)
const appState = proxy({
count: 0,
text: 'Merhaba Valtio!',
user: {
name: 'John Doe',
age: 30,
},
items: ['elma', 'armut'],
get itemCount() { // Türetilmiş durum
return this.items.length;
},
});
Şimdi itemCount‘a doğrudan snap.itemCount üzerinden erişebilirsiniz. items dizisi değiştiğinde, itemCount otomatik olarak güncellenecek ve bunu kullanan bileşenler yeniden oluşturulacaktır.
5. Asenkron İşlemler
Asenkron işlemler (API çağrıları gibi) valtio ile çok doğal bir şekilde entegre olur. Sadece bir fonksiyon içinde proxy nesnesini güncelleyin:
// store.js (devam)
export const fetchUserData = async (userId) => {
// Yüklenme durumunu güncelleyebiliriz
appState.loading = true;
appState.error = null;
try {
const response = await fetch(/api/users/${userId});
const data = await response.json();
appState.user = data; // API'den gelen veriyi doğrudan atayın
} catch (error) {
appState.error = error.message;
} finally {
appState.loading = false;
}
};
Bileşenlerinizde snap.loading, snap.error ve snap.user gibi özelliklere erişerek bu asenkron işlemin durumunu kolayca görüntüleyebilirsiniz.
React Values (Valtio ile) Yaklaşımının Avantajları
valtio gibi “React Values” felsefesini benimseyen kütüphaneler, geleneksel durum yönetimi çözümlerine göre bir dizi önemli avantaj sunar:
1. Basitlik ve Sezgisellik
* Düz JavaScript Nesneleri: Durumunuzu tanımlamak için karmaşık yapılar öğrenmenize gerek yok. Bildiğiniz JavaScript nesnelerini kullanırsınız.
* Doğrudan Mutasyon: Durumu güncellemek için setState, dispatch veya özel eylemler yerine, nesne özelliklerini doğrudan değiştirirsiniz. Bu, özellikle yeni başlayanlar için öğrenme eğrisini önemli ölçüde azaltır.
* Minimal API: Öğrenilecek çok az sayıda fonksiyon ve hook vardır (proxy, useSnapshot). Bu, geliştiricilerin kütüphranenin detaylarına takılmak yerine, uygulamanın iş mantığına odaklanmasını sağlar.
2. Performans
* İnce Taneli Reaktivite: valtio, Proxy nesneleri sayesinde bir bileşenin yalnızca eriştiği durum özelliği değiştiğinde yeniden oluşturulmasını tetikler. Bu, Context API‘nin aksine, bağlamdaki herhangi bir değişiklikte tüm tüketicilerin yeniden oluşturulmasını önleyerek gereksiz yeniden oluşturmaları ve performans sorunlarını minimize eder.
* Optimizasyon: useSnapshot hook’u, React’in dahili mekanizmalarını kullanarak performansı optimize eder ve yalnızca gerekli güncellemelerin yapılmasını sağlar.
3. Geliştirici Deneyimi (DX)
* Daha Az Kalıp Kod: Eylemler, azaltıcılar, seçiciler gibi katmanlara gerek kalmadığı için daha az kod yazılır. Bu, geliştirme hızını artırır ve kod tabanını daha sade hale getirir.
* Kolay Hata Ayıklama: Durumunuz, tarayıcının geliştirici araçlarında kolayca incelenebilen basit bir JavaScript nesnesidir. Karmaşık bir Redux store yapısını veya MobX gözlem ağacını anlamaya çalışmaktan daha kolaydır.
* Mükemmel TypeScript Desteği: valtio, TypeScript ile sorunsuz bir şekilde çalışır ve durum nesneleriniz için güçlü tip güvenliği sağlar. Bu, büyük ölçekli uygulamalarda hata yakalamayı kolaylaştırır.
4. Esneklik
* Çok Yönlü Kullanım: valtio, hem yerel bileşen durumu, hem global uygulama durumu, hem de bileşenler arası paylaşılan durum için kullanılabilir.
* Modülerlik: Büyük durumları daha küçük, odaklanmış proxy nesnelerine bölerek uygulamanızın durum yönetimini modüler hale getirebilirsiniz.
* Diğer React Özellikleriyle Entegrasyon: React.memo, useCallback gibi diğer React optimizasyonlarıyla uyumlu bir şekilde çalışır.
React Values Ne Zaman Kullanılmalı, Ne Zaman Kullanılmamalı?
Her araç gibi, valtio gibi “React Values” kütüphaneleri de belirli senaryolarda parlar ve bazı durumlarda ise alternatif çözümler daha uygun olabilir.
React Values Kullanımının İdeal Olduğu Senaryolar:
* Çoğu Küçük ve Orta Ölçekli Uygulama: Basit ve orta karmaşıklıktaki uygulamaların büyük çoğunluğu için valtio, aşırı karmaşıklıktan kaçınarak mükemmel bir durum yönetimi çözümü sunar.
* Form Yönetimi: Karmaşık form durumlarını yönetmek, alan değerlerini güncellemek ve doğrulama durumlarını tutmak için son derece sezgiseldir.
* Kullanıcı Tercihleri ve Arayüz Durumu: Tema ayarları, dil seçimi, bildirim tercihleri veya UI bileşenlerinin açılıp kapanma durumları gibi global UI durumları için idealdir.
* Prop Drilling’den Kaçınma: Bileşen ağacının derinliklerinde paylaşılan durumlar için Context API‘nin performans dezavantajlarına düşmeden prop drilling’i ortadan kaldırır.
* Redux/MobX’e Daha Basit Bir Alternatif Arayanlar: Uygulamanız Redux’un tüm karmaşıklığına ihtiyaç duymuyorsa veya ekosistemin öğrenme eğrisinden kaçınmak istiyorsanız, valtio harika bir seçenektir.
* TypeScript Odaklı Projeler: Doğrudan JavaScript nesneleriyle çalışması ve güçlü tip çıkarımı sayesinde TypeScript ile çok iyi uyum sağlar.
Alternatif Çözümlerin Göz Önünde Bulundurulması Gereken Senaryolar:
* Çok Büyük ve Karmaşık Uygulamalar: Eğer uygulamanızın durumu aşırı derecede karmaşık ve farklı modüller arasında çok sıkı entegrasyon gerektiriyorsa, Redux’un katı mimarisi ve middleware ekosistemi (örneğin, zaman yolculuğu hata ayıklaması, logger’lar, persistans) daha uygun olabilir.
* Zaman Yolculuğu Hata Ayıklama (Time-Travel Debugging) İhtiyacı: Redux DevTools gibi araçlar, durumun her adımını geri alıp ileri sararak hata ayıklama imkanı sunar. valtio‘da bu tür bir yerleşik özellik yoktur (ancak harici kütüphanelerle entegre edilebilir).
* Kesinlikle Değişmezlik (Immutability) Gereksinimi: valtio doğrudan mutasyonu teşvik etse de, JavaScript’in Proxy yapısı sayesinde arka planda immutable snapshot’lar sunar. Ancak, eğer projeniz baştan sona katı bir immutable veri akışı felsefesini benimsiyorsa, Redux gibi kütüphaneler daha doğal bir uyum sağlayabilir (yine de valtio Immer ile birlikte kullanılabilir).
* Mevcut Ekip Bilgisi: Ekibiniz zaten Redux veya MobX konusunda derinlemesine bilgiye sahipse ve bu çözümlerle verimli çalışıyorsa, yeni bir kütüphaneye geçiş yapmak her zaman en iyi seçenek olmayabilir.
Diğer Çözümlerle Karşılaştırma
valtio‘yu diğer popüler durum yönetimi çözümleriyle karşılaştırmak, hangi durumlarda öne çıktığını daha iyi anlamamızı sağlar:
valtio vs. useState / useReducer
* Avantaj: valtio, global veya bileşenler arası paylaşılan durumlar için prop drilling’i ortadan kaldırır. useState ve useReducer genellikle bileşenlerin kendi iç durumları için daha uygundur.
* Dezavantaj: valtio, çok basit, izole edilmiş bileşen durumları için useState‘den biraz daha fazla kurulum gerektirebilir (bir proxy nesnesi oluşturma).
valtio vs. Context API
* Avantaj: valtio, ince taneli reaktivite sayesinde Context API‘nin neden olduğu gereksiz yeniden oluşturmaları önler. Context API, bir bağlam değeri değiştiğinde tüm tüketicileri yeniden oluşturma eğilimindedir. valtio ayrıca daha az kalıp kod gerektirir (Provider/Consumer sarmalayıcılarına gerek yok).
* Dezavantaj: Context API, React’in yerleşik bir parçasıdır ve ek bir kütüphaneye bağımlılık yaratmaz. Ancak bu avantaj, performans dezavantajıyla dengelenir.
valtio vs. Redux
* Avantaj: valtio, Redux’a göre çok daha az kalıp kod, daha basit bir API ve daha kolay bir öğrenme eğrisi sunar. Doğrudan mutasyon yaklaşımı, durumu güncelleme sürecini daha sezgisel hale getirir. Performans açısından da ince taneli güncellemeler sunar.
* Dezavantaj: Redux, çok büyük ve karmaşık uygulamalar için güçlü middleware ekosistemi, zaman yolculuğu hata ayıklama ve katı bir mimari ile daha fazla kontrol sunar. valtio bu seviyede bir denetim mekanizması sağlamaz.
valtio vs. MobX
* Benzerlikler: Hem valtio hem de MobX, reaktif programlama prensiplerine dayanır ve doğrudan mutasyonu teşvik eder. İkisi de ince taneli reaktivite sunar.
* Farklılıklar: MobX daha büyük ve kapsamlı bir ekosistemdir, kendi gözlemlenebilir nesne ve reaksiyon sistemine sahiptir. valtio ise daha minimaldir, doğrudan JavaScript Proxy’lerini ve React hook’larını kullanarak daha az soyutlama ile benzer sonuçlar elde eder. MobX’in öğrenme eğrisi valtio‘dan biraz daha dik olabilir.
En İyi Uygulamalar ve İpuçları
valtio ile çalışırken en iyi sonuçları elde etmek için bazı ipuçları:
* Mağazalarınızı Odaklanmış Tutun: Büyük bir proxy nesnesi yerine, uygulamanızın farklı alanları için birden fazla küçük proxy nesnesi oluşturun (örneğin, userStore, productStore, uiStore). Bu, kodun daha düzenli olmasını ve sorumlulukların ayrılmasını sağlar.
* Mantığı UI’dan Ayırın: Durum güncelleme mantığını (asenkron işlemler, veri işleme) React bileşenlerinin dışındaki store.js dosyası gibi ayrı bir yerde tutun. Bileşenler sadece durumu okumalı ve güncelleme fonksiyonlarını çağırmalıdır.
* TypeScript Kullanın: valtio, TypeScript ile harika çalışır. proxy nesnelerinizin tiplerini tanımlayarak, geliştirme sırasında hataları yakalayın ve kod tamamlama özelliklerinden yararlanın.
* Gereksiz Yeniden Oluşturmaları İnceleyin: React DevTools Profiler‘ı kullanarak bileşenlerinizin ne zaman ve neden yeniden oluşturulduğunu gözlemleyin. valtio‘nun ince taneli reaktivitesi sayesinde genellikle daha az yeniden oluşturma görürsünüz, ancak bazen React.memo gibi optimizasyonlar hala faydalı olabilir.
* useSnapshot‘ı Doğru Kullanın: useSnapshot‘tan dönen nesnenin immutable olduğunu ve doğrudan değiştirilmemesi gerektiğini unutmayın. Tüm güncellemeler orijinal proxy nesnesi üzerinden yapılmalıdır.
Sonuç
React uygulamalarında durum yönetimi, geliştirme sürecinin temel bir parçasıdır. Geleneksel yaklaşımların getirdiği karmaşıklıklar ve performans zorlukları, daha basit ve daha verimli çözümlere olan ihtiyacı artırmıştır. “React Values” felsefesini benimseyen valtio gibi kütüphaneler, bu ihtiyaca modern ve güçlü bir yanıt sunar.
valtio, düz JavaScript nesnelerini reaktif hale getirerek, doğrudan mutasyonla durum güncelleme kolaylığını, ince taneli reaktivite ile üstün performansı ve minimal bir API ile mükemmel bir geliştirici deneyimini bir araya getirir. Prop drilling sorununu çözer, Context API‘nin gereksiz yeniden oluşturma sorunlarını aşar ve Redux gibi kütüphanelerin getirdiği kalıp kod yükünü önemli ölçüde azaltır.
Çoğu küçük ve orta ölçekli uygulama için valtio, durum yönetimi için son derece cazip ve etkili bir seçenektir. Daha az kod yazarak, daha az karmaşıklıkla uğraşarak ve daha yüksek performans elde ederek geliştiricilerin iş mantığına odaklanmasını sağlar.
Eğer React projelerinizde durum yönetimiyle ilgili zorluklar yaşıyorsanız veya daha basit, daha modern bir yaklaşıma geçmek istiyorsanız, valtio‘yu denemenizi şiddetle tavsiye ederiz. React Values felsefesi, durum yönetimini gerçekten “kolay” hale getirmenin yeni bir yolunu sunuyor.
