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ı,
useMemobağı (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
dispatchister, 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 DI | React Context | |
|---|---|---|
| Ne taşır | Servis örneği — davranış, bağımlılıklarıyla kurulur | Herhangi bir değer — çoğunlukla state + dispatch |
| Değişince | Örnek genelde sabittir; içindeki veri akışa (RxJS/signal) bağlıdır | Value her değişimde tüm tüketicileri render eder |
| Asıl işi | Bağımlılıkları kurmak, paylaşmak, testte değiştirmek | Ağ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
- Drilling'i önce yaşa:
oturum'u üç seviye prop'laKullaniciRozeti'ne indir; ara bileşenlerin imzalarındaki kirliliği gör. Sonra yalnız bileşimle (children) drilling'i kaldır — context'siz. - Bu dersteki
OturumSaglayicikalıbını kur (iki context, reducer, iki fırlatan hook); giriş/çıkış düğmeleriniuseOturumDispatch'e, rozetiuseOturum'a bağla. - 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. - 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. - En yakın Provider kuralı: rozetin etrafına farklı değerli ikinci bir
TemaContext.Providersar; iç dalın onu, dışarının üsttekini okuduğunu gör. - 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."
