Her React geliştiricisi eninde sonunda aynı duvara toslar. En üst seviyedeki App bileşeninizde bir kullanıcı nesnesi (user object) çekersiniz. Sonra bunu aşağıya aktarırsınız. Ve tekrar aşağıya. Bir rota sarmalayıcısından (route wrapper), bir düzen şablonundan (layout shell), bir yan menü konteynerinden geçirerek; sadece üç katman derindeki küçücük bir avatar bileşeninin profil resmini gösterebilmesi için bunu yaparsınız. Aradaki bileşenlerin o kullanıcı nesnesiyle hiçbir işi yoktur. Onlar sadece paketi ileten kuryelerdir. İşte buna prop drilling denir ve temiz bir bileşen ağacını sinir bozucu bir kulaktan kulağa oyununa dönüştürür.
Asıl sancı, verinin yapısı değiştiğinde başlar. Belki de backend, user.avatar yerine user.profile.avatar şeklinde iç içe geçmiş bir yapı sunmaya başlar. Bir anda, veriyi kendisi hiç kullanmayan beş farklı dosyadaki TypeScript arayüzlerini (interfaces) veya PropTypes tanımlarını düzenlemek zorunda kalırsınız. İşte React Context API tam bu noktada devreye girer.
Context Veri Akışını Nasıl Yeniden Yapılandırır
Context'i evinizin merkezinde duran bir Wi-Fi yönlendiricisi (router) gibi düşünün. O olmasaydı, dizüstü bilgisayarınıza sinyal ulaştırmak için her odadan sarkan Ethernet kablolarına ihtiyacınız olurdu. O varken ise yönlendirici sinyali havadan yayınlar ve doğru şifreye sahip her cihaz doğrudan bağlanabilir. Duvarların bir önemi kalmaz.
React terimleriyle ifade edersek; uygulamanızın kök dizini (root), her katmandan bir kurye gibi davranmasını istemeden veri yayınlayabilir. Herhangi bir iç içe geçmiş bileşen bu yayına abone olabilir ve tam olarak ihtiyacı olanı alabilir.
Üç Temel Parça
Context API, üç hareketli parçadan oluşur.
React.createContext() yayın kanalını kurar. İçinde bir Provider ve (eski kodlarda) bir Consumer barındıran bir nesne döndürür. Belirli bir özellik için bunu yalnızca bir kez çağırmanız yeterlidir.
The Provider, ağacınızın bir bölümünü sarmalayan bir bileşendir. value adı verilen tek bir prop kabul eder. Bu prop'a ne koyarsanız koyun, ne kadar derinde olurlarsa olsunlar, tüm alt bileşenler için erişilebilir hale gelir.
useContext, bir fonksiyonel bileşenin bu yayına bağlanmasını sağlayan Hook'tur. Bileşeninizin içinde, oluşturduğunuz context nesnesini useContext içine aktarırsınız ve o da size mevcut değeri döndürür. İşte bu kadar. Sarmalayıcılar yok, ekstra prop'lar yok.
Hook'lar gelmeden önce, render props ile Consumer desenini kullanmak zorundaydınız. Çalışıyordu ancak çok fazla girinti (indentation) ve sarmalayıcı kalabalığı yaratıyordu. useContext, tüm bunları fonksiyon gövdesi içinde tek bir satıra indirgeyerek sadeleştirdi.
Context Ne Zaman Gerçekten Mantıklıdır
Alışkanlık gereği hemen Context'e sarılmayın. Context, ağacınızın farklı dallarındaki birçok ilgisiz bileşenin paylaştığı veriler için tasarlanmıştır. İyi adaylar şunlardır:
- Tema ayarları. Sadece açık veya koyu mod değil; boşluk değerleri (spacing tokens), renk paletleri ve yazı tipi ölçekleri. Bunları her stilize edilmiş buton ve modal aracılığıyla manuel olarak iletmek çok çabuk yorucu hale gelir.
- Kullanıcı kimlik doğrulaması. Giriş durumu, izinler dizisi veya mevcut kullanıcı nesnesi. Üst bilgi çubuğunuz (header bar), bir gösterge panelindeki widget ve özel bir rota koruyucusu (private route guard) ağacın tamamen farklı köşelerinde bulunabilir.
- Dil tercihleri. Yerel ayar dizeleri (locale strings), tarih formatları ve para birimi sembolleri. Form etiketleri gibi en uçtaki bileşenlerin, yoldaki her ebeveynin bu bilgiden haberdar olmasına gerek kalmadan bunlara ihtiyacı vardır.
- Alışveriş sepeti verileri. Ürün sayısı, toplam tutar ve sepete ekleme fonksiyonları. Üst bilgi rozeti (header badge) ve ödeme sayfası aynı duruma ihtiyaç duyar, ancak genellikle tamamen farklı düzen dalları altında bulunurlar.
Pratik Bir Tema Değiştirici
Context'in işleyişini görmenin en net yollarından biri bir tema değiştiricidir (theme toggle). Gerçekten önemli olan detayları atlamadan bunu nasıl kurabileceğinizi aşağıda görebilirsiniz.
İlk olarak, bir ThemeContext.js dosyası oluşturun. React.createContext() çağrısını yapın ve sonucu saklayın. Ardından, mevcut temayı useState veya useReducer ile yöneten bir ThemeProvider bileşeni oluşturun. Çocukları (children), hem mevcut temayı hem de temayı değiştirecek bir fonksiyonu içeren bir nesne geçirerek context'inizin Provider'ı ile sarmalayın. Hem ThemeProvider bileşenini hem de context nesnesinin kendisini dışa aktarın (export).
İkinci olarak, uygulamanızın giriş noktasına gidin. ThemeProvider'ı içe aktarın ve tüm uygulamanızı bununla sarmalayın. Eğer bu adımı atlarsanız, daha sonra context'i okumaya çalışan her şey yalnızca varsayılan değeri görecektir.
Üçüncü olarak, bir Header veya Content bileşeni içinde, context nesnesini ve useContext'i içe aktarın. Hook'u çağırın, temayı ve değiştirme fonksiyonunu parçalara ayırın (destructure) ve CSS sınıflarınızı koşullu olarak uygulayın. Değiştirme fonksiyonunu çağıran bir buton ekleyin. Bileşen, üst bileşeninden asla bir theme prop'u almaz. Sinyali doğrudan havadan çeker.
Prop Drilling, Context mi yoksa Redux mu?
Bu araçlar arasında seçim yapmak sadakatle değil, daha çok state yapınızla ilgilidir.
Prop drilling, iki veya üç derinlik seviyesi için gayet uygundur. Açık bir yöntemdir, IDE'nizde izlemesi kolaydır ve bağımlılıkları belirgin tutar. Sorunlar, ancak aynı prop'u altı veya yedi katman boyunca geçirmeye başladığınızda ortaya çıkar.
Context API, React ile birlikte gelir. Bu, ekstra paket boyutu (bundle size) ve harici bir kurulum gerektirmediği anlamına gelir. Küçük ve orta ölçekli global state'i, özellikle temalar veya kullanıcı profilleri gibi seyrek değişen verileri harika bir şekilde yönetir.
Redux, ek kütüphanelerin kurulmasını ve boilerplate kod yazılmasını gerektirir. State mantığınız karmaşık olduğunda, birden fazla state slice'ı derin yollarla etkileşime girdiğinde veya time-travel debugging ve middleware'e ihtiyaç duyduğunuzda kendini amorti eder. Basit global veriler için Redux gereğinden fazladır (overkill).
Kimsenin Konuşmadığı Performans Gerçeği
İşte junior uygulamaları senior uygulamalardan ayıran püf noktası: Bir Context Provider değeri değiştiğinde, o context'i tüketen her bileşen yeniden render edilir. Bileşenin ilgilendiği özel slice'ın aynı kalıp kalmaması önemli değildir. React yeni referansı görür ve bir güncelleme planlar.
Tüm uygulama state'inizi tek bir devasa StoreContext içine boşaltırsanız, aslında tüm UI'ınızı birbirine yapıştırmış olursunuz. Bir tema ayarını değiştirmek; alışveriş sepetinizi, dashboard grafiklerinizi ve bildirim listenizi yeniden render eder. Bu, gereksiz bir iştir.
Context'lerinizi domain'lere göre ayırın. Görsel ayarlar için bir ThemeContext, profil verileri için bir UserContext ve ticaret durumu için bir CartContext tutun. Eğer bir kullanıcı görünen adını düzenlerse, ürün ızgarasına dokunmadan header'ınız güncellenir. Ayrıca, Provider value prop'una ne geçirdiğinize dikkat edin. Render sırasında satır içinde bir nesne literal'ı { theme, toggleTheme } geçerseniz, her render'da yeni bir referans oluşturur ve gereksiz güncellemeleri tetiklersiniz. Eğer değer fonksiyonlar veya ilkel olmayan (non-primitive) veriler içeriyorsa, bu yapıyı useMemo ile stabilize edin.
Saatler Kaybettiren Hatalar
İki hata, ekipleri tekrar tekrar yakalar.
Context nesnesini export etmeyi unutmak. ThemeProvider bileşenini export edip sonra useContext(ThemeProvider) çağırmaya çalışmak kolaydır. Ancak işleyiş böyle değildir. Hook'un sarmalayıcı bileşene (wrapper component) değil, createContext tarafından döndürülen context nesnesine ihtiyacı vardır. Eğer sadece Provider'ı export ederseniz, tüketicilerinizin (consumers) içe aktarabileceği bir şey olmaz.
useContext'i Provider dışında çağırmak. Hook, createContext'e geçtiğiniz varsayılan değeri döndürür. Eğer bir varsayılan değer geçmediyseniz, undefined alırsınız. Eğer bileşen ağacınız tüketiciyi (consumer) DOM'da Provider'dan daha yukarıda render ediyorsa veya Provider tamamen eksikse, veriniz basitçe ulaşmayacaktır. Index veya root dosyanızın uygulamayı gerçekten sarmaladığından emin olun.
Asıl Çıkarılması Gereken Ders
React Context bir state yönetimi devrimi değildir. Belirli bir mekansal problem için hedeflenmiş bir araçtır: Her katmanı bir postane haline getirmeden veriyi uzak bileşenlere ulaştırmak. Onu gerçekten global veriler için kullanın, render performansını korumak için context'lerinizi domain'lere göre ayırın ve sinyali okumaya çalışmadan önce ağacınızı her zaman doğru Provider ile sarmalayın. Bu alışkanlıkları oturtursanız, bileşen ağaçlarınız temiz, hızlı ve mantığını kavraması kolay kalacaktır.
