Skip to content

Bölüm 8 / 16

Context API

Prop drilling ve çözümü.

11 dakikalık okuma

2. derste adı kondu: prop drilling — bir değerin, onunla hiç işi olmayan ara bileşenlerden elden ele taşınması. Bu ders React'in yerleşik cevabını işler: Context, bir değeri ağacın bir dalına "yayınlar"; dalın altındaki herhangi bir bileşen değeri aradan kimseye dokunmadan okur. Ama iki düzeltmeyle: drilling'in ilk ilacı context değil bileşimdir ve context bir DI konteyneri değildir — Angular'dan gelen refleks burada bilinçli bırakılır. Oturum örneği üzerinde context + reducer kalıbını, value nesnesinin render yayılımı tuzağını ve context'i bölme tekniğini kuruyoruz; "ne zaman context, ne zaman store" çizgisi 9. derse köprü olacak.

01Ne öğreneceksin

  • Prop drilling'in gerçek maliyeti ve ilk ilaç: bileşim (children ile ara katmanı şeffaflaştırmak)
  • Üç parça: createContext / Provider / useContext — ve varsayılan değer tuzağı
  • Context + reducer kalıbı: oturum yönetimi, sağlayıcı bileşen, fırlatan custom hook
  • Value nesnesi render yayılımı: her tüketicinin yeniden render olması, useMemo bağı (6. ders)
  • Context bölme: state ile dispatch'i ayırmak; selector yokluğunun sonucu
  • "Context DI değildir" — Angular kıyası; ne zaman context, ne zaman store (9. derse köprü)

02Drilling'in maliyeti ve ilk ilaç: bileşim

Sorunu adıyla koyalım: oturum bilgisi en üstte doğuyor, Ustbar → Menu → KullaniciRozeti zincirinde yalnız en alttaki kullanıyor — ama ikisi de prop'u taşımak zorunda. Maliyet taşımanın zahmeti değil, bağlaşımdır: aradaki her bileşenin imzası, ilgilenmediği bir veriyle kirlenir; veri şekli değişince zincirin tamamı düzeltilir.

Kıdemli refleks context'e atlamadan önce bileşimi dener: ara bileşen, içeriğini prop olarak (çoğunlukla children, 1. dersteki ng-content köprüsü) dışarıdan alırsa, taşıyıcı olmaktan çıkar:

// ❌ Menu, oturum'u yalnız aşağı postalamak için imzasına almış
<Menu oturum={oturum} />
function Menu({ oturum }: { oturum: Oturum }) {
  return <nav>...<KullaniciRozeti oturum={oturum} /></nav>;
}

// ✅ bileşim: Menu içeriğini dışarıdan alır, oturum'dan haberi bile yok
<Menu><KullaniciRozeti oturum={oturum} /></Menu>
function Menu({ children }: { children: ReactNode }) {
  return <nav>...{children}</nav>;
}

Kullanan bileşenle veriyi üreten bileşen aynı yerde buluştu; ara katman şeffaflaştı. Bir-iki seviyelik drilling'in doğru cevabı çoğu zaman budur ve hiçbir yeni mekanizma istemez. Context, bileşimin yetmediği yerde devreye girer: değer ağacın her yerinden okunacaksa (tema, oturum, dil) buluşturacak tek bir üst nokta yoktur — yayın gerekir.

03Üç parça ve varsayılan değer tuzağı

Mekanizma üç parçadır: createContext kanalı modül seviyesinde tanımlar, Provider ağacın bir dalına değeri yayınlar, useContext en yakın Provider'ın değerini okur:

const TemaContext = createContext<"koyu" | "acik">("koyu");
// "koyu" = VARSAYILAN: yalnız üstte HİÇ Provider yoksa kullanılır

function Uygulama() {
  const [tema, setTema] = useState<"koyu" | "acik">("koyu");
  return (
    <TemaContext.Provider value={tema}>
      <Pano />                {/* dalın TAMAMI okuyabilir */}
    </TemaContext.Provider>
  );
}

function DurumRozeti() {      // kaç seviye derinde olduğu önemsiz
  const tema = useContext(TemaContext);
  return <span className={`rozet rozet-${tema}`}>...</span>;
}

Okuma kuralı Angular'ın hiyerarşik injector'ından tanıdıktır: en yakın Provider kazanır — iç dala farklı değerli ikinci bir Provider koyarsan o dal onu görür. Asıl tuzak varsayılan değerdedir: varsayılan, "Provider'daki başlangıç değeri" değildir; yalnız bileşen Provider'sız render edildiğinde devreye giren yedektir. Oturum gibi "mutlaka sağlayıcı ister" verilerde anlamlı bir varsayılan yoktur ve uydurmak hatayı sessizleştirir: Provider'ı unutursun, bileşen sahte varsayılanla çalışır, hata ekrana kadar sızar. Kıdemli kalıp tersini yapar — varsayılanı null bırak, okuma noktasında fırlat (bir sonraki bölümde useOturum'da göreceksin): yanlış kullanım ilk render'da, net mesajla patlar. 3. hattın "hatayı erken ve gürültülü yakala" ilkesinin React hâli.

04Context + reducer: oturum kalıbı

Context yalnız taşır; değerin nasıl değiştiği hâlâ state'in işidir. Uygulama geneli veriler (oturum, tema) için yerleşik kalıp, 6. dersin useReducer'ını context'le evlendirir — tamamı tek dosyada yaşar:

type Oturum = { kullanici: Kullanici | null };
type OturumEylemi =                              // eylem = olay (6. dersin kuralı)
  | { tur: "giris-yapildi"; kullanici: Kullanici }
  | { tur: "cikis-yapildi" };

function oturumReducer(o: Oturum, e: OturumEylemi): Oturum {
  switch (e.tur) {
    case "giris-yapildi": return { kullanici: e.kullanici };
    case "cikis-yapildi": return { kullanici: null };
  }
}

const OturumContext = createContext<Oturum | null>(null);
const OturumDispatchContext =
  createContext<Dispatch<OturumEylemi> | null>(null);

export function OturumSaglayici({ children }: { children: ReactNode }) {
  const [oturum, dispatch] = useReducer(oturumReducer, { kullanici: null });
  return (
    <OturumContext.Provider value={oturum}>
      <OturumDispatchContext.Provider value={dispatch}>
        {children}
      </OturumDispatchContext.Provider>
    </OturumContext.Provider>
  );
}

export function useOturum(): Oturum {            // fırlatan custom hook (7. ders)
  const oturum = useContext(OturumContext);
  if (oturum === null)
    throw new Error("useOturum, OturumSaglayici içinde çağrılmalıdır");
  return oturum;
}
export function useOturumDispatch(): Dispatch<OturumEylemi> {
  const dispatch = useContext(OturumDispatchContext);
  if (dispatch === null)
    throw new Error("useOturumDispatch, OturumSaglayici içinde çağrılmalıdır");
  return dispatch;
}

Kalıbın üç imzası: sağlayıcı bir bileşendir (OturumSaglayici) — state, reducer ve iki Provider tek pakette, uygulama köküne tek satırla takılır; context nesneleri dışa açılmaz — dünyaya yalnız useOturum/useOturumDispatch verilir, yanlış kullanım fırlatır, çağıranlar null kontrolünden kurtulur (tip de Oturum | null değil Oturum döner — 9. hattın daraltma disiplini); okuyan ve yazan ayrı kanaldadır — bunun neden önemli olduğu bir sonraki bölümün konusu.

05Render yayılımı: context'in bedeli

Context'in performans sözleşmesi tek cümledir ve pazarlıksızdır: Provider'ın value'su Object.is ile değiştiğinde, o context'i okuyan tüm bileşenler yeniden render olur — arada React.memo olsa bile. Selector yoktur: "ben value'nun yalnız şu alanını okuyorum" diyemezsin; kanalı dinleyen herkes her yayında uyanır. Bu sözleşmeden iki pratik kural çıkar:

  • Value nesnesi tuzağı. Render içinde yazılan value={{ oturum, dispatch }} her render'da yeni nesnedir (2. dersin referans sözleşmesi) — üst bileşen alakasız bir sebeple render olsa da tüm tüketiciler uyanır. İlaç 6. dersten: const deger = useMemo(() => ({ oturum, dispatch }), [oturum]) — referans, içerik değişmedikçe sabit kalır. Bu, useMemo'nun "referans kararlılığı" kullanımının ders kitabı örneğidir.
  • Context'i böl. Okuyanlarla yazanlar farklı kitlelerdir: "çıkış" düğmesi yalnız dispatch ister, kullanıcı rozeti yalnız veriyi. İkisini tek value'da birleştirirsen düğme, her oturum değişiminde boşuna render olur. Yukarıdaki kalıp bu yüzden iki Provider taşır: dispatch'in kimliği zaten sabit (6. ders) — OturumDispatchContext'in value'su hiç değişmez, yalnız yazan bileşenler hiçbir yayında uyanmaz. Aynı mantıkla, birbirinden bağımsız değişen veriler (tema ve oturum gibi) tek dev context'e değil ayrı context'lere konur.

Bu bedel context'i "sık değişen veri" için yanlış araç yapar: saniyede birkaç kez değişen bir değeri (imleç konumu, canlı fiyat) context'e koymak, ağacın yarısını metronom gibi render etmektir. Sık güncelleme + seçici abonelik ihtiyacının aracı 9. dersin store'larıdır.

06Context DI değildir

Angular'dan gelen el, context'te DI arar; benzerlik gerçek ama kısmidir. Çözümleme kuralı benzer: useContext en yakın Provider'ı bulur, Angular injector hiyerarşide en yakın sağlayıcıyı. Fark, taşınan şeyde ve tepkide:

Angular DIReact Context
Ne taşırServis örneği — davranış, bağımlılıklarıyla kurulurHerhangi bir değer — çoğunlukla state + dispatch
DeğişinceÖrnek genelde sabittir; içindeki veri akışa (RxJS/signal) bağlıdırValue her değişimde tüm tüketicileri render eder
Asıl işiBağımlılıkları kurmak, paylaşmak, testte değiştirmekAğaç geneli değeri drilling'siz ulaştırmak

Pratik sonuç: Angular'da her paylaşılan iş servise gider, React'te "servis" dediğin şey çoğu zaman import edilen bir modülün fonksiyonudur — API istemcisini context'e koymak gerekmez, import yeter; test ederken de modül sahtelenir. Context'e yalnız render'ı ilgilendiren, ağaç geneli, düşük frekanslı değerler girer.

07Karar çizgisi: bileşim → context → store

Dersin araçlarını tek merdivene dizelim. Bir-iki seviyelik taşıma → önce bileşim (02. bölüm), yeni mekanizma kurulmaz. Tema/oturum/dil gibi az değişen, ağaç geneli değer → context + reducer kalıbı (04. bölüm). Sık güncellenen, seçici abonelik ve araç desteği (DevTools, ara katman) isteyen istemci durumu → 9. dersin store'ları — context'in selector yokluğu (05. bölüm) tam orada çözülür. Sunucudan gelen veri → 10. dersin ayrı dünyası; ekran adresi olan durum (seçili sekme, filtre) → 11. dersin URL'si. Context bu merdivenin bir basamağıdır, varış noktası değil — merdivenin tamamını 9. ders "karar merdiveni" adıyla önüne koyacak.

08Mini alıştırma — Oturumu yayına al

Yaklaşık 20 dakika · kurulum yok — StackBlitz projenin üstüne kur

  1. Drilling'i önce yaşa: oturum'u üç seviye prop'la KullaniciRozeti'ne indir; ara bileşenlerin imzalarındaki kirliliği gör. Sonra yalnız bileşimle (children) drilling'i kaldır — context'siz.
  2. Bu dersteki OturumSaglayici kalıbını kur (iki context, reducer, iki fırlatan hook); giriş/çıkış düğmelerini useOturumDispatch'e, rozeti useOturum'a bağla.
  3. Tuzağı kanıtla: sağlayıcıya bilerek value={{ oturum, dispatch }} tek nesnesini ver ve üst bileşene alakasız bir sayaç ekle; Profiler'da her tikte tüm tüketicilerin render olduğunu gör. useMemo + bölünmüş context'e dönüp farkı doğrula.
  4. Varsayılan değer tuzağını sahnele: OturumSaglayici'yı kaldır ve fırlatan hook'un ilk render'da net mesajla patladığını gör; sonra varsayılanı sahte bir oturumla doldurup hatanın nasıl sessizleştiğini göster — hangi davranışın üretimde daha güvenli olduğunu bir cümleyle yaz.
  5. En yakın Provider kuralı: rozetin etrafına farklı değerli ikinci bir TemaContext.Provider sar; iç dalın onu, dışarının üsttekini okuduğunu gör.
  6. Karar antrenmanı: şu dört veriyi merdivene yerleştir ve gerekçele — imleç altındaki satırın id'si, seçili tema, sunucudan gelen başvuru listesi, formdaki taslak metin.

09Kendini sına

Prop drilling nedir, gerçek maliyeti nedir ve context'e uzanmadan önce hangi tekniği denersin?

Prop drilling, bir değerin onu hiç kullanmayan ara bileşenler üzerinden elden ele hedefe taşınmasıdır. Gerçek maliyet taşıma zahmeti değil bağlaşımdır: aradaki her bileşenin imzası ilgilenmediği veriyle kirlenir ve verinin şekli değişince zincirin tamamına dokunulur. İlk ilaç context değil bileşimdir: ara bileşen içeriğini children (ya da başka bir ReactNode prop'u) olarak dışarıdan alırsa taşıyıcı olmaktan çıkar — veriyi üreten bileşenle kullanan bileşen aynı yerde buluşur, ara katman şeffaflaşır. Bir-iki seviyelik drilling'in doğru cevabı çoğunlukla budur ve yeni mekanizma gerektirmez. Context, bileşimin yetmediği durumun aracıdır: değer ağacın her yerinden okunacaksa (tema, oturum, dil) buluşturacak tek üst nokta yoktur — değeri dala yayınlarsın ve isteyen okur.

createContext'in varsayılan değeri ne zaman devreye girer? Oturum gibi verilerde hangi kalıp doğrudur ve neden?

Varsayılan değer, Provider'daki başlangıç değeri değildir; yalnız bileşen ağacın üstünde hiç Provider yokken useContext çağrılırsa dönen yedektir. Tuzak şurada: oturum gibi "mutlaka sağlayıcı ister" verilere anlamlı bir varsayılan uydurmak, Provider'ı unutma hatasını sessizleştirir — bileşen sahte değerle çalışır, yanlışlık ekrana kadar sızar. Doğru kalıp: varsayılanı null bırak, context nesnesini modülden dışa açma ve okumayı fırlatan bir custom hook'un arkasına koy — useOturum, değer null ise "OturumSaglayici içinde çağrılmalıdır" diye ilk render'da, net mesajla patlar. Kazanç üçlüdür: hata erken ve gürültülü yakalanır; çağıranlar null kontrolünden kurtulur; tip Oturum | null yerine daraltılmış Oturum döner. Ek kural: en yakın Provider kazanır — iç dala konan ikinci Provider o dal için değeri geçersiz kılar.

Context'in render yayılımı sözleşmesini anlat: value nesnesi tuzağı nedir, context bölme neyi çözer ve dispatch'in bunda özel yeri nedir?

Sözleşme: Provider'ın value'su Object.is ile değiştiğinde o context'i okuyan tüm bileşenler yeniden render olur — selector yoktur, "yalnız şu alanı okuyorum" denemez ve aradaki React.memo korumaz. Value nesnesi tuzağı bundan doğar: render içinde yazılan value={{ oturum, dispatch }} her render'da yeni referanstır; üst bileşen alakasız sebeple render olsa bile tüm tüketiciler uyanır. İlaç useMemo ile referans sabitlemektir. Context bölme daha köklü çözer: okuyanlar (veri isteyen rozet) ve yazanlar (yalnız dispatch isteyen çıkış düğmesi) ayrı kitlelerdir; state ve dispatch ayrı context'lere konursa yazan taraf veri değişimlerinde hiç uyanmaz — çünkü dispatch'in kimliği React garantisiyle sabittir, o context'in value'su hiç değişmez. Aynı ilkeyle bağımsız değişen veriler (tema ve oturum) tek dev context'e değil ayrı context'lere konur. Bu bedel yüzünden sık değişen değerler context'e girmez — o iş seçici abonelikli store'ların alanıdır.

"Context, React'in DI'ı" diyen Angular kökenli birine düzeltmeyi yap ve "ne zaman bileşim / context / store" karar çizgisini çiz.

Benzerlik çözümlemede: useContext en yakın Provider'ı bulur, Angular injector hiyerarşide en yakın sağlayıcıyı — buraya kadar paralel. Fark taşınan şeyde ve tepkide: DI, davranış taşıyan servis örneklerini kurar ve paylaşır; örnek genelde sabittir, testte değiştirilebilir. Context ise render'a bağlı bir değer yayınıdır: value her değiştiğinde tüm tüketiciler render olur. Pratik düzeltme: React'te "servis" çoğu zaman import edilen modül fonksiyonudur — API istemcisi context'e konmaz, import edilir; context'e yalnız render'ı ilgilendiren, ağaç geneli, düşük frekanslı değerler girer. Karar çizgisi: bir-iki seviyelik taşıma → önce bileşim (children); az değişen ağaç geneli değer (tema, oturum, dil) → context + reducer kalıbı; sık güncellenen, seçici abonelik ve araç desteği isteyen istemci durumu → store (Zustand/Redux Toolkit); sunucudan gelen veri → sunucu durumu araçları (TanStack Query). Context merdivenin bir basamağıdır, her derdin cevabı değil.

10Sınav köşesi

Yazılıda nasıl sorulur

(1) Prop drilling nedir; asıl maliyeti nedir? (2) Bileşim (children) drilling'i nasıl azaltır? (3) Context'in üç parçası nedir; okuma hangi Provider'dan yapılır? (4) createContext'in varsayılan değeri ne zaman kullanılır; tuzağı nedir? (5) Fırlatan custom hook kalıbı hangi üç faydayı sağlar? (6) Provider value'su değişince hangi bileşenler render olur; React.memo korur mu? (7) Value nesnesi neden useMemo'ya alınır? (8) State ve dispatch neden ayrı context'lere konur? (9) Context ile Angular DI arasındaki temel fark nedir? (10) Hangi veri context'e, hangisi store'a, hangisi sunucu durumu aracına gider?

Sözlüde nasıl anlatılır — 2 dakikalık taslak

Düzen: (1) Sorun — "Drilling'in maliyeti bağlaşımdır: ara imzalar kirlenir; ilk ilacım bileşimdir — children ile ara katmanı şeffaflaştırırım, context'e ancak değer ağacın her yerinden okunacaksa geçerim." (2) Mekanizma — "createContext kanal, Provider yayın, useContext en yakın Provider'dan okuma; varsayılan değer yalnız Provider'sız durumda devrededir — oturum gibi verilerde null bırakıp fırlatan hook'la korurum." (3) Kalıp — "Uygulama genel durumu için context + reducer: sağlayıcı bileşen, dışa yalnız iki hook." (4) Bedel — "Value değişince tüm tüketiciler render olur, selector yok; value'yu useMemo'yla sabitler, state ile dispatch'i ayırırım — dispatch sabit olduğundan yazan taraf hiç uyanmaz." (5) Kıdem vuruşu — "Context DI değildir: servis import edilir, context'e yalnız düşük frekanslı ağaç geneli değer girer; sık güncellenen durum store'un, sunucu verisi Query'nin işidir."