React state yönetimi için kapsamlı bir rehber. useState, Context API, useReducer ve Redux, Zustand, TanStack Query gibi popüler kütüphaneleri keşfedin.
React State Yönetiminde Uzmanlaşma: Küresel Geliştirici Rehberi
Front-end geliştirme dünyasında, state yönetimi en kritik zorluklardan biridir. React kullanan geliştiriciler için bu zorluk, basit bir bileşen seviyesi endişesinden, bir uygulamanın ölçeklenebilirliğini, performansını ve sürdürülebilirliğini tanımlayabilen karmaşık bir mimari karara dönüştü. İster Singapur'da tek başına çalışan bir geliştirici, ister Avrupa'ya yayılmış dağıtık bir ekibin parçası, ister Brezilya'da bir startup kurucusu olun, React state yönetiminin genel yapısını anlamak, sağlam ve profesyonel uygulamalar oluşturmak için esastır.
Bu kapsamlı rehber, React'in yerleşik araçlarından güçlü harici kütüphanelere kadar, state yönetiminin tüm spektrumunda size yol gösterecektir. Her yaklaşımın arkasındaki 'neden'i keşfedecek, pratik kod örnekleri sunacak ve dünyanın neresinde olursanız olun projeniz için doğru aracı seçmenize yardımcı olacak bir karar çerçevesi sunacağız.
React'te 'State' Nedir ve Neden Bu Kadar Önemlidir?
Araçlara dalmadan önce, 'state' kavramı hakkında net ve evrensel bir anlayış oluşturalım. Özünde, state, uygulamanızın belirli bir andaki durumunu tanımlayan herhangi bir veridir. Bu herhangi bir şey olabilir:
- Bir kullanıcı şu anda giriş yapmış mı?
- Bir form giriş alanında hangi metin var?
- Bir modal pencere açık mı kapalı mı?
- Bir alışveriş sepetindeki ürünlerin listesi nedir?
- Şu anda bir sunucudan veri çekiliyor mu?
React, kullanıcı arayüzünün state'in bir fonksiyonu olduğu ilkesi üzerine kurulmuştur (UI = f(state)). State değiştiğinde, React bu değişikliği yansıtmak için kullanıcı arayüzünün gerekli kısımlarını verimli bir şekilde yeniden render eder. Zorluk, bu state'in bileşen ağacında doğrudan ilişkili olmayan birden fazla bileşen tarafından paylaşılması ve değiştirilmesi gerektiğinde ortaya çıkar. İşte bu noktada state yönetimi, kritik bir mimari endişe haline gelir.
Temel: useState
ile Yerel (Local) State
Her React geliştiricisinin yolculuğu useState
hook'u ile başlar. Bu, tek bir bileşene özgü (yerel) bir state parçası beyan etmenin en basit yoludur.
Örneğin, basit bir sayacın state'ini yönetmek:
import React, { useState } from 'react';
function Counter() {
// 'count' state değişkenidir
// 'setCount' ise onu güncelleyen fonksiyondur
const [count, setCount] = useState(0);
return (
Butona {count} kez tıkladınız
);
}
useState
, form girdileri, açma/kapama anahtarları veya durumu uygulamanın diğer bölümlerini etkilemeyen herhangi bir kullanıcı arayüzü öğesi gibi paylaşılması gerekmeyen state'ler için mükemmeldir. Sorun, başka bir bileşenin `count` değerini bilmesi gerektiğinde başlar.
Klasik Yaklaşım: State'i Yukarı Taşıma ve Prop Drilling
React'te bileşenler arasında state paylaşmanın geleneksel yolu, onu en yakın ortak atalarına "yukarı taşımaktır". State daha sonra props aracılığıyla alt bileşenlere akar. Bu, temel ve önemli bir React desenidir.
Ancak, uygulamalar büyüdükçe bu durum "prop drilling" olarak bilinen bir soruna yol açabilir. Bu, bir prop'u, veriye aslında ihtiyaç duymayan birden çok ara bileşen katmanı üzerinden, yalnızca veriye ihtiyaç duyan derinde yuvalanmış bir alt bileşene ulaştırmak için geçirmek zorunda kaldığınızda meydana gelir. Bu, kodun okunmasını, yeniden düzenlenmesini ve bakımını zorlaştırabilir.
Bir kullanıcının tema tercihinin (örneğin, 'dark' veya 'light') bileşen ağacının derinliklerindeki bir düğme tarafından erişilmesi gerektiğini hayal edin. Bunu şu şekilde geçirmeniz gerekebilir: Uygulama -> Düzen -> Sayfa -> Başlık -> TemaDeğiştirmeDüğmesi
. Bu prop'u yalnızca Uygulama
(state'in tanımlandığı yer) ve TemaDeğiştirmeDüğmesi
(kullanıldığı yer) umursar, ancak Düzen
, Sayfa
ve Başlık
aracılık yapmak zorunda kalır. İşte daha gelişmiş state yönetimi çözümlerinin çözmeyi amaçladığı sorun budur.
React'in Yerleşik Çözümleri: Context ve Reducer'ların Gücü
Prop drilling zorluğunun farkında olan React ekibi, Context API'yi ve useReducer
hook'unu tanıttı. Bunlar, harici bağımlılıklar eklemeden önemli sayıda state yönetimi senaryosunu halledebilen güçlü, yerleşik araçlardır.
1. Context API: State'i Küresel Olarak Yayınlama
Context API, verileri bileşen ağacında her seviyede manuel olarak props geçmek zorunda kalmadan iletmenin bir yolunu sunar. Bunu, uygulamanızın belirli bir bölümü için küresel bir veri deposu olarak düşünebilirsiniz.
Context kullanımı üç ana adımı içerir:
- Context'i Oluşturun: Bir context nesnesi oluşturmak için `React.createContext()` kullanın.
- Context'i Sağlayın: Bileşen ağacınızın bir bölümünü sarmak ve ona bir `value` geçirmek için `Context.Provider` bileşenini kullanın. Bu sağlayıcı içindeki herhangi bir bileşen bu değere erişebilir.
- Context'i Tüketin: Context'e abone olmak ve mevcut değerini almak için bir bileşen içinde `useContext` hook'unu kullanın.
Örnek: Context kullanarak basit bir tema değiştirici
// 1. Context'i oluşturun (örneğin, theme-context.js dosyasında)
import { createContext, useState } from 'react';
export const ThemeContext = createContext();
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'));
};
// value nesnesi, tüm tüketici bileşenler tarafından kullanılabilir olacak
const value = { theme, toggleTheme };
return (
{children}
);
}
// 2. Context'i sağlayın (örneğin, ana App.js dosyanızda)
import { ThemeProvider } from './theme-context';
import MyPage from './MyPage';
function App() {
return (
);
}
// 3. Context'i tüketin (örneğin, derinde yuvalanmış bir bileşende)
import { useContext } from 'react';
import { ThemeContext } from './theme-context';
function ThemeToggleButton() {
const { theme, toggleTheme } = useContext(ThemeContext);
return (
);
}
Context API'nin Artıları:
- Yerleşik: Harici kütüphanelere gerek yoktur.
- Basitlik: Basit küresel state için anlaşılması kolaydır.
- Prop Drilling'i Çözer: Birincil amacı, props'ları birçok katman üzerinden geçirmekten kaçınmaktır.
Eksileri ve Performans Değerlendirmeleri:
- Performans: Sağlayıcıdaki değer değiştiğinde, o context'i tüketen tüm bileşenler yeniden render edilir. Eğer context değeri sık sık değişiyorsa veya tüketen bileşenlerin render edilmesi maliyetliyse bu bir performans sorunu olabilir.
- Yüksek Frekanslı Güncellemeler İçin Uygun Değil: Tema, kullanıcı kimlik doğrulaması veya dil tercihi gibi düşük frekanslı güncellemeler için en uygunudur.
2. useReducer
Hook'u: Öngörülebilir State Geçişleri İçin
useState
basit state'ler için harikayken, useReducer
daha karmaşık state mantığını yönetmek için tasarlanmış daha güçlü kardeşidir. Özellikle birden çok alt değer içeren veya bir sonraki state'in bir öncekine bağlı olduğu durumlarda kullanışlıdır.
Redux'tan esinlenen useReducer
, bir `reducer` fonksiyonu ve bir `dispatch` fonksiyonu içerir:
- Reducer Fonksiyonu: Mevcut `state`'i ve bir `action` nesnesini argüman olarak alan ve yeni state'i döndüren saf bir fonksiyondur. `(state, action) => newState`.
- Dispatch Fonksiyonu: Bir state güncellemesini tetiklemek için bir `action` nesnesi ile çağırdığınız bir fonksiyondur.
Örnek: Artırma, azaltma ve sıfırlama eylemlerine sahip bir sayaç
import React, { useReducer } from 'react';
// 1. Başlangıç state'ini tanımlayın
const initialState = { count: 0 };
// 2. Reducer fonksiyonunu oluşturun
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return initialState;
default:
throw new Error('Beklenmedik eylem türü');
}
}
function ReducerCounter() {
// 3. useReducer'ı başlatın
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Sayı: {state.count}
{/* 4. Kullanıcı etkileşiminde eylemleri dispatch edin */}
>
);
}
useReducer
kullanmak, state güncelleme mantığınızı tek bir yerde (reducer fonksiyonunda) merkezileştirir, bu da onu daha öngörülebilir, test edilmesi daha kolay ve özellikle mantık karmaşıklığı arttıkça daha sürdürülebilir hale getirir.
Güçlü İkili: useContext
+ useReducer
React'in yerleşik hook'larının gerçek gücü, useContext
ve useReducer
'ı birleştirdiğinizde ortaya çıkar. Bu desen, herhangi bir harici bağımlılık olmadan sağlam, Redux benzeri bir state yönetimi çözümü oluşturmanıza olanak tanır.
- `useReducer` karmaşık state mantığını yönetir.
- `useContext`, `state`'i ve `dispatch` fonksiyonunu onlara ihtiyaç duyan herhangi bir bileşene yayınlar.
Bu desen harikadır çünkü `dispatch` fonksiyonunun kendisi kararlı bir kimliğe sahiptir ve yeniden render'lar arasında değişmez. Bu, yalnızca eylemleri `dispatch` etmesi gereken bileşenlerin, state değeri değiştiğinde gereksiz yere yeniden render edilmeyeceği anlamına gelir ve yerleşik bir performans optimizasyonu sağlar.
Örnek: Basit bir alışveriş sepetini yönetme
// 1. cart-context.js içinde kurulum
import { createContext, useReducer, useContext } from 'react';
const CartStateContext = createContext();
const CartDispatchContext = createContext();
const cartReducer = (state, action) => {
switch (action.type) {
case 'ADD_ITEM':
// Bir öğe ekleme mantığı
return [...state, action.payload];
case 'REMOVE_ITEM':
// id'ye göre bir öğeyi kaldırma mantığı
return state.filter(item => item.id !== action.payload.id);
default:
throw new Error(`Bilinmeyen eylem: ${action.type}`);
}
};
export const CartProvider = ({ children }) => {
const [state, dispatch] = useReducer(cartReducer, []);
return (
{children}
);
};
// Kolay tüketim için özel hook'lar
export const useCart = () => useContext(CartStateContext);
export const useCartDispatch = () => useContext(CartDispatchContext);
// 2. Bileşenlerde kullanım
// ProductComponent.js - yalnızca bir eylem dispatch etmesi gerekiyor
function ProductComponent({ product }) {
const dispatch = useCartDispatch();
const handleAddToCart = () => {
dispatch({ type: 'ADD_ITEM', payload: product });
};
return ;
}
// CartDisplayComponent.js - yalnızca state'i okuması gerekiyor
function CartDisplayComponent() {
const cartItems = useCart();
return Sepetteki Ürünler: {cartItems.length};
}
State ve dispatch'i iki ayrı context'e bölerek bir performans avantajı elde ederiz: Yalnızca eylem gönderen `ProductComponent` gibi bileşenler, sepetin state'i değiştiğinde yeniden render edilmez.
Harici Kütüphanelere Ne Zaman Başvurulmalı?
useContext
+ useReducer
deseni güçlüdür, ancak her derde deva değildir. Uygulamalar ölçeklendikçe, özel harici kütüphaneler tarafından daha iyi hizmet verilen ihtiyaçlarla karşılaşabilirsiniz. Şu durumlarda harici bir kütüphane düşünmelisiniz:
- Gelişmiş bir middleware ekosistemine ihtiyacınız olduğunda: Günlük kaydı (logging), eşzamansız API çağrıları (thunks, sagas) veya analitik entegrasyonu gibi görevler için.
- İleri düzey performans optimizasyonları gerektiğinde: Redux veya Jotai gibi kütüphaneler, temel bir Context kurulumundan daha etkili bir şekilde gereksiz yeniden render'ları önleyen son derece optimize edilmiş abonelik modellerine sahiptir.
- Zaman yolculuğuyla hata ayıklama (Time-travel debugging) bir öncelik olduğunda: Redux DevTools gibi araçlar, zaman içindeki state değişikliklerini incelemek için inanılmaz derecede güçlüdür.
- Sunucu tarafı state'ini yönetmeniz gerektiğinde (önbellekleme, senkronizasyon): TanStack Query gibi kütüphaneler bu iş için özel olarak tasarlanmıştır ve manuel çözümlerden çok daha üstündür.
- Küresel state'iniz büyük ve sık güncellendiğinde: Tek ve büyük bir context, performans darboğazlarına neden olabilir. Atomik state yöneticileri bu durumu daha iyi idare eder.
Popüler State Yönetim Kütüphaneleriyle Küresel Bir Tur
React ekosistemi, her birinin kendi felsefesi ve ödünleşimleri olan geniş bir state yönetimi çözümleri yelpazesi sunan canlı bir yapıya sahiptir. Dünya çapındaki geliştiriciler için en popüler seçeneklerden bazılarını keşfedelim.
1. Redux (& Redux Toolkit): Yerleşik Standart
Redux, yıllardır baskın state yönetimi kütüphanesi olmuştur. State değişikliklerini öngörülebilir ve izlenebilir kılan katı bir tek yönlü veri akışını zorunlu kılar. İlk zamanlardaki Redux, boilerplate (tekrar eden standart kod) miktarıyla bilinirken, Redux Toolkit (RTK) kullanan modern yaklaşım süreci önemli ölçüde basitleştirmiştir.
- Temel Kavramlar: Tek bir küresel `store`, tüm uygulama state'ini tutar. Bileşenler ne olduğunu açıklamak için `action`'lar `dispatch` eder. `Reducer`'lar, mevcut state'i ve bir eylemi alıp yeni state'i üreten saf fonksiyonlardır.
- Neden Redux Toolkit (RTK)? RTK, Redux mantığı yazmanın resmi, önerilen yoludur. Store kurulumunu basitleştirir, `createSlice` API'si ile boilerplate'i azaltır ve kolay değişmez (immutable) güncellemeler için Immer ve kutudan çıktığı gibi eşzamansız mantık için Redux Thunk gibi güçlü araçlar içerir.
- Temel Gücü: Olgun ekosistemi eşsizdir. Redux DevTools tarayıcı uzantısı dünya standartlarında bir hata ayıklama aracıdır ve middleware mimarisi karmaşık yan etkileri (side effects) yönetmek için inanılmaz derecede güçlüdür.
- Ne Zaman Kullanılmalı: Öngörülebilirlik, izlenebilirlik ve sağlam bir hata ayıklama deneyiminin her şeyden önemli olduğu, karmaşık ve birbiriyle bağlantılı küresel state'e sahip büyük ölçekli uygulamalar için.
2. Zustand: Minimalist ve Dayatmacı Olmayan Seçenek
Almancada "durum" (state) anlamına gelen Zustand, minimalist ve esnek bir yaklaşım sunar. Genellikle Redux'a daha basit bir alternatif olarak görülür ve boilerplate olmadan merkezi bir store'un avantajlarını sağlar.
- Temel Kavramlar: Bir `store`'u basit bir hook olarak oluşturursunuz. Bileşenler, state'in bölümlerine abone olabilir ve güncellemeler, state'i değiştiren fonksiyonları çağırarak tetiklenir.
- Temel Gücü: Basitlik ve minimal API. Başlaması inanılmaz derecede kolaydır ve küresel state'i yönetmek için çok az kod gerektirir. Uygulamanızı bir sağlayıcı (provider) ile sarmalamaz, bu da onu herhangi bir yere entegre etmeyi kolaylaştırır.
- Ne Zaman Kullanılmalı: Küçük ve orta ölçekli uygulamalar için veya hatta Redux'un katı yapısı ve boilerplate'i olmadan basit, merkezi bir store istediğiniz daha büyük uygulamalar için.
// store.js
import { create } from 'zustand';
const useBearStore = create((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
}));
// MyComponent.js
function BearCounter() {
const bears = useBearStore((state) => state.bears);
return Buralarda {bears} ayı var...
;
}
function Controls() {
const increasePopulation = useBearStore((state) => state.increasePopulation);
return ;
}
3. Jotai & Recoil: Atomik Yaklaşım
Jotai ve Recoil (Facebook'tan), "atomik" state yönetimi kavramını popüler hale getirmiştir. Tek bir büyük state nesnesi yerine, state'inizi "atom" adı verilen küçük, bağımsız parçalara ayırırsınız.
- Temel Kavramlar: Bir `atom`, bir state parçasını temsil eder. Bileşenler, bireysel atomlara abone olabilir. Bir atomun değeri değiştiğinde, yalnızca o belirli atomu kullanan bileşenler yeniden render edilir.
- Temel Gücü: Bu yaklaşım, Context API'nin performans sorununu cerrahi bir hassasiyetle çözer. React benzeri bir zihinsel model sunar (`useState`'e benzer ancak küreseldir) ve yeniden render'lar son derece optimize edildiği için varsayılan olarak mükemmel performans sunar.
- Ne Zaman Kullanılmalı: Çok sayıda dinamik, bağımsız küresel state parçasına sahip uygulamalarda. Context güncellemelerinizin çok fazla yeniden render'a neden olduğunu fark ettiğinizde Context'e harika bir alternatiftir.
4. TanStack Query (önceden React Query): Sunucu Tarafı State'inin Kralı
Belki de son yıllardaki en önemli paradigma kayması, "state" olarak adlandırdığımız şeyin çoğunun aslında sunucu state'i olduğunun farkına varılmasıdır — yani bir sunucuda yaşayan ve istemci uygulamamızda çekilen, önbelleğe alınan ve senkronize edilen veriler. TanStack Query genel amaçlı bir state yöneticisi değildir; sunucu state'ini yönetmek için özel bir araçtır ve bunu olağanüstü iyi yapar.
- Temel Kavramlar: Veri çekmek için `useQuery` ve veri oluşturmak/güncellemek/silmek için `useMutation` gibi hook'lar sağlar. Önbellekleme, arka planda yeniden veri çekme, stale-while-revalidate mantığı, sayfalama ve çok daha fazlasını kutudan çıktığı gibi halleder.
- Temel Gücü: Veri çekme işlemlerini önemli ölçüde basitleştirir ve sunucu verilerini Redux veya Zustand gibi küresel bir state yöneticisinde saklama ihtiyacını ortadan kaldırır. Bu, istemci tarafı state yönetimi kodunuzun büyük bir bölümünü ortadan kaldırabilir.
- Ne Zaman Kullanılmalı: Uzak bir API ile iletişim kuran hemen hemen her uygulamada. Dünya çapında birçok geliştirici artık bunu yığınlarının (stack) vazgeçilmez bir parçası olarak görüyor. Genellikle, TanStack Query (sunucu state'i için) ve `useState`/`useContext` (basit UI state'i için) kombinasyonu bir uygulamanın ihtiyaç duyduğu tek şeydir.
Doğru Seçimi Yapmak: Bir Karar Çerçevesi
Bir state yönetimi çözümü seçmek bunaltıcı gelebilir. İşte seçiminize rehberlik edecek pratik, küresel olarak uygulanabilir bir karar çerçevesi. Kendinize bu soruları sırayla sorun:
-
State gerçekten küresel mi, yoksa yerel olabilir mi?
Her zamanuseState
ile başlayın. Kesinlikle gerekli olmadıkça küresel state kullanmayın. -
Yönettiğiniz veri aslında sunucu state'i mi?
Eğer bir API'den gelen veriyse, TanStack Query kullanın. Bu, önbellekleme, veri çekme ve senkronizasyonu sizin için halleder. Muhtemelen uygulamanızın "state"inin %80'ini yönetecektir. -
Kalan UI state'i için sadece prop drilling'den kaçınmanız mı gerekiyor?
Eğer state sık güncellenmiyorsa (örneğin, tema, kullanıcı bilgisi, dil), yerleşik Context API mükemmel, bağımlılıksız bir çözümdür. -
UI state mantığınız karmaşık ve öngörülebilir geçişlere mi sahip?
useReducer
'ı Context ile birleştirin. Bu size harici kütüphaneler olmadan state mantığını yönetmek için güçlü, organize bir yol sunar. -
Context ile performans sorunları mı yaşıyorsunuz veya state'iniz birçok bağımsız parçadan mı oluşuyor?
Jotai gibi atomik bir state yöneticisi düşünün. Gereksiz yeniden render'ları önleyerek mükemmel performansa sahip basit bir API sunar. -
Katı, öngörülebilir bir mimari, middleware ve güçlü hata ayıklama araçları gerektiren büyük ölçekli bir kurumsal uygulama mı geliştiriyorsunuz?
Bu, Redux Toolkit için en uygun kullanım durumudur. Yapısı ve ekosistemi, büyük ekiplerde karmaşıklık ve uzun vadeli sürdürülebilirlik için tasarlanmıştır.
Özet Karşılaştırma Tablosu
Çözüm | En Uygun Olduğu Alan | Temel Avantajı | Öğrenme Eğrisi |
---|---|---|---|
useState | Yerel bileşen state'i | Basit, yerleşik | Çok Düşük |
Context API | Düşük frekanslı küresel state (tema, kimlik doğrulama) | Prop drilling'i çözer, yerleşik | Düşük |
useReducer + Context | Harici kütüphanesiz karmaşık UI state'i | Organize mantık, yerleşik | Orta |
TanStack Query | Sunucu state'i (API veri önbellekleme/senkronizasyon) | Büyük miktarda state mantığını ortadan kaldırır | Orta |
Zustand / Jotai | Basit küresel state, performans optimizasyonu | Minimal boilerplate, harika performans | Düşük |
Redux Toolkit | Karmaşık, paylaşılan state'e sahip büyük ölçekli uygulamalar | Öngörülebilirlik, güçlü geliştirici araçları, ekosistem | Yüksek |
Sonuç: Pragmatik ve Küresel Bir Bakış Açısı
React state yönetimi dünyası artık bir kütüphanenin diğerine karşı savaşı değil. Farklı araçların farklı sorunları çözmek için tasarlandığı sofistike bir manzaraya dönüştü. Modern, pragmatik yaklaşım, ödünleşimleri anlamak ve uygulamanız için bir 'state yönetimi araç kiti' oluşturmaktır.
Dünya genelindeki çoğu proje için güçlü ve etkili bir yığın şu şekilde başlar:
- Tüm sunucu state'i için TanStack Query.
- Paylaşılmayan, basit tüm UI state'i için
useState
. - Basit, düşük frekanslı küresel UI state'i için
useContext
.
Ancak bu araçlar yetersiz kaldığında Jotai, Zustand veya Redux Toolkit gibi özel bir küresel state kütüphanesine başvurmalısınız. Sunucu state'i ile istemci state'i arasında net bir ayrım yaparak ve önce en basit çözümle başlayarak, ekibinizin büyüklüğü veya kullanıcılarınızın konumu ne olursa olsun, performanslı, ölçeklenebilir ve bakımı keyifli uygulamalar oluşturabilirsiniz.