aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Web Geliştirme

React Context API ile Global State Yönetimi

React context, bir bileşen ağacında veriyi tek tek props olarak elden ele taşımadan paylaşmanın resmi yoludur. Eğer tema, oturum açmış kullanıcı ya da seçili dil gibi bir değeri beş kat aşağıdaki bir bileşene ulaştırmak için aradaki her bileşene gereksiz prop eklediyseniz, "prop drilling" denen sorunu yaşıyorsunuz demektir. Bu yazıda Context API'yi doğru kurmayı, custom hook ile sarmalamayı, en sık düşülen performans tuzaklarını ve ne zaman context'e hiç başvurmamak gerektiğini somut kod üzerinden anlatıyorum.

Prop drilling tam olarak nedir?

Diyelim ki en üstte oturum bilgisi var ve onu yalnızca derinlerdeki bir Avatar bileşeni kullanıyor. Arada duran Layout, Sidebar, UserMenu bileşenleri bu veriyi hiç kullanmadıkları halde sadece aşağı geçirmek için prop kabul etmek zorunda kalır:

function App() {
  const [user, setUser] = useState(null);
  return <Layout user={user} />;
}
// Layout -> Sidebar -> UserMenu -> Avatar
// her biri "user" prop'unu sadece bir alttakine pas geçer

Sorun şu: ara bileşenler veriye bağımlı olmadıkları halde imzaları kirleniyor, refactor zorlaşıyor ve bir prop'un adını değiştirmek tüm zinciri etkiliyor. Context bu zinciri kısa devre yaptırır: değeri yukarıda bir kez sağlar, aşağıda doğrudan okursunuz.

En küçük çalışan örnek

Context üç parçadan oluşur: createContext ile bağlamı oluşturmak, bir Provider ile değeri ağaca yaymak ve useContext ile o değeri okumak.

import { createContext, useContext, useState } from "react";

const ThemeContext = createContext("light");

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState("light");
  const toggle = () => setTheme(t => (t === "light" ? "dark" : "light"));
  return (
    <ThemeContext.Provider value={{ theme, toggle }}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useTheme() {
  return useContext(ThemeContext);
}

Ardından uygulamanın kökünü provider ile sarın ve herhangi bir alt bileşende hook'u çağırın:

function ThemeButton() {
  const { theme, toggle } = useTheme();
  return <button onClick={toggle}>Tema: {theme}</button>;
}

function App() {
  return (
    <ThemeProvider>
      <ThemeButton />
    </ThemeProvider>
  );
}

Artık ThemeButton ile App arasında kaç katman olursa olsun, hiçbiri theme prop'u taşımak zorunda değil.

Custom hook ile güvenli erişim

Yukarıda useTheme adında küçük bir custom hook yazdım; bu kalıbı her zaman önerin. İki faydası var: context'in iç yapısını çağıran taraftan gizler ve provider olmadan kullanılması durumunda anlamlı bir hata verebilir.

export function useTheme() {
  const ctx = useContext(ThemeContext);
  if (ctx === undefined) {
    throw new Error("useTheme yalnızca <ThemeProvider> içinde kullanılabilir");
  }
  return ctx;
}

Burada başlangıç değerini createContext(undefined) yaparsanız, provider'ı eklemeyi unuttuğunuzda sessizce yanlış varsayılan dönmek yerine net bir hata alırsınız. Bu, ekipçe çalışırken hata ayıklamayı ciddi biçimde hızlandırır.

En büyük performans tuzağı: gereksiz render

Context'in en çok yanlış anlaşılan yanı şudur: provider'ın value'su değiştiğinde, o context'i tüketen tüm bileşenler yeniden render olur. Sorun, her render'da yeni bir nesne referansı oluşturmaktan çıkar:

// KÖTÜ: her render'da yeni { } nesnesi -> tüm tüketiciler yeniden render
<ThemeContext.Provider value={{ theme, toggle }}>

Provider bileşeni başka bir sebeple render olsa bile bu satır yeni bir nesne üretir ve React referansları Object.is ile karşılaştırdığı için "değişti" sayar. Çözüm, değeri useMemo ile sabitlemektir:

const value = useMemo(() => ({ theme, toggle }), [theme]);
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;

İkinci bir teknik de bağlamı ayırmaktır: sık değişen veri (ör. anlık metin girişi) ile nadiren değişen fonksiyonları (ör. dispatch) iki ayrı context'e koyarsanız, hızlı değişen değer yalnızca onu okuyan bileşenleri etkiler. Genel kural: bir context ne kadar çok ve birbirinden bağımsız şey taşırsa, gereksiz render riski o kadar artar.

useReducer ile birleştirmek

State mantığı büyüdükçe useState yerine useReducer kullanıp context ile birleştirmek temiz bir kalıptır. Bu, küçük bir global "store" gibi çalışır ama harici kütüphane gerektirmez:

const CartContext = createContext(null);

function cartReducer(state, action) {
  switch (action.type) {
    case "add":    return [...state, action.item];
    case "remove": return state.filter(i => i.id !== action.id);
    default:       return state;
  }
}

export function CartProvider({ children }) {
  const [items, dispatch] = useReducer(cartReducer, []);
  const value = useMemo(() => ({ items, dispatch }), [items]);
  return <CartContext.Provider value={value}>{children}</CartContext.Provider>;
}

dispatch fonksiyonu React tarafından sabit (stabil) tutulur, yani onu memo bağımlılıklarında güvenle kullanabilirsiniz.

Ne zaman context kullanmamalı?

Context her sorunun çözümü değildir. Aşağıdaki durumlarda başka bir yola bakın:

  • Yüksek frekanslı, geniş etkili güncellemeler (ör. her tuş vuruşunda binlerce abone): context tek başına performans için optimize edilmiş bir state yöneticisi değildir; Zustand, Jotai veya Redux Toolkit gibi araçlar bu senaryoda daha verimlidir.
  • Sunucu verisi (API'den gelen, önbelleğe alınması, yeniden denenmesi gereken veri): bunun için TanStack Query gibi bir veri katmanı context'ten çok daha doğru bir araçtır.
  • Sadece bir-iki seviye derinlik: prop drilling iki katmansa, çoğu zaman prop geçmek context kurmaktan daha okunaklıdır.

Context'in tatlı noktası, nadiren değişen ama çok yerde okunan verilerdir: tema, kimlik doğrulama durumu, dil/yerelleştirme, özellik bayrakları.

Sık Sorulan Sorular

Context, Redux'un yerini tutar mı?

Kısmen. Context bir "veri taşıma" mekanizmasıdır, tam teşekküllü bir state yönetim kütüphanesi değildir. Basit global durumlar için useReducer + context yeterlidir; ancak ara katman (middleware), devtools, seçici (selector) bazlı render optimizasyonu gibi ihtiyaçlar büyüdüğünde Redux Toolkit veya Zustand devreye girer.

Birden fazla context iç içe kullanmak sorun mu?

Hayır, yaygın ve sağlıklı bir kalıptır. Tema, kullanıcı ve sepet için ayrı provider'lar kullanmak hem performans hem okunabilirlik açısından genellikle tek dev context'ten daha iyidir. İç içe geçen provider'ları tek bir AppProviders bileşeninde toplayarak kökü temiz tutabilirsiniz.

useMemo gerçekten gerekli mi?

Provider değeri bir nesne ya da fonksiyon içeriyorsa ve provider yeniden render olabiliyorsa, evet. Değer ilkel bir tip (string, number, boolean) ise React referans yerine değeri karşılaştırır ve useMemo'ya gerek kalmaz.

React mimarinizde state akışını sadeleştirmek mi istiyorsunuz? Prop drilling'den arınmış, performanslı bir context yapısı kurmanıza yardımcı olabilirim — benimle iletişime geçin.

Bu kategorideki tüm yazılar →

Devamı için