React State Management: Redux vs Context API

Ezran Bayantemur
Kodcular
Published in
8 min readJul 8, 2020

React ile geliştirme yapan herkesin en çok ihtiyaç duyduğu yapılardan biri component’lar arasında kullanılabilecek global bir state yapısıdır. Oturum açan kullanıcının bilgileri, uygulama arayüz dili, eğer raporlama uygulaması ise ana veri gibi her sayfadan erişebilir bir state ihtiyacı ile çoğu zaman karşılaşıyoruz. Redux ve Context API bu noktada bize cevap veren iki adet yaklaşım. Fakat aralarındaki farklar ne?

Bir bakalım..

Kutsal kitaplara göre tarihteki ilk cinayet ve ilk kardeş katli. Habil ile Kabil

Başlamadan önce, neden ihtiyaç var?

Kullanılan component sayısı ve proje gereksinimleri arttıkça karmaşıklık da bununla doğru orantılı olarak yükselir. Componentlar arasında veri geçişleri sıklaşır ve iş props passing’den props drilling’e doğru ilerlemeye başlar. Yani kullandığımız component’lar arasındaki veri geçişi aşırı boyuta ulaşır ve takip edilebilir noktayı geçmiş olur. Oysa bizim istediğimiz şey veri transferinin stabil bir düzeyde olmasıdır.

Örneğin;

Ekteki örnek kodumuzda MainPage adlı component’imizden yola çıkan user ve language adlı iki state’imizin yaptıkları keyifli boğaz turunu görüyoruz. Kendileri prop olarak atana atana ProductPage adlı component’imizde evrimlerini userProps ve languages olarak tamamlıyorlar. Bu geliştiricinin kafasında “Bunlar nereden gelmişti?” ve “Bunlar neyin verisi?” sorularını oluşturacak ve baş ağrıtacaktır. Hepsinden öte yazılan kodun okunulabilirliğini azaltacaktır.

Oysa örnek kodda user ve language statelerini global bir formata çekip props yapısından kurtarsaydık;

elimizde sadece gerekli propların geçirildiği sade bir yapı kalmış olurdu.

Global state yaklaşımı bu noktada bizim yardımımıza koşan bir yapı görevini üstleniyor. Geliştirme yapacağımız her bir component’da ilgili global state’e erişim sağlayarak prop geçirme işleminde kurtulmuş oluyoruz. Haliyle daha okunabilir bir kod, daha dinamik bir yapı, daha kısa bir boğaz turu 👍

Peki nasıl işliyor bu global state olayı?

State Management

Genel anlamda bakacak olursak state management yapılarındaki yaklaşım aslında aynı kalıp üzerinde ilerliyor. Bunu önümüzdeki örneklerde daha iyi gözlemleyeceğiz fakat öncesinde ufak bir bakış atacak olursak elimizdeki yapılar hep aynı.

store: Global olarak saklamak istediğimiz verileri oluşturduğumuz depo alanı

reducer: Global verileri güncellediğimiz fonksiyonlar bütünü

provider: Component’lerimizi sarmallayan (bir nevi yöneten) context bileşeni

State management yapısı aslında bu üç temel bileşenden oluşmakta. İster Redux kullanın ister Context API, her ikisi de aynı kalıbı paylaşıyor aynı mantıkta ilerliyor. Siz projenizde bir store oluşturuyor ve global state’lerinizi yaratıyorsunuz. Reducer ile bu state’leri güncelleyeceğiniz fonksiyonları oluşturuyorsunuz. Provider ile projenizi sarmallıyor ve gerekli bilgileri provider’a sağlıyorsunuz. Bu işlemleri tek bir kere yaptıktan sonra istediğiniz bileşenden context’inize erişim sağlayabiliyor, global state’lerinizi yönetbiliyorsunuz.

Başlangıçta çok karışık görünse de aslında olabildiğine sade bir pattern. Özellikle hooks ile kullanımı çok daha kolaylaşmış bir durumda. Kurduğumuz yapı yine React bileşenlerinden oluşturduğumuz bir tasarım aslında. Aynı OOP dillerinde uygulanan Singleton, Factory ya da Proxy gibi bir “design pattern”. Daha farklı bir şey değil.

İsterseniz kod kısmına geçelim!

Context API

Context API yaklaşımı React’ın kendi saf context yapısını kullanarak global bir state management tasarlamaya dayanıyor. Aslında Redux’ın da temelinde de aynı dizayn mevcut fakat Redux bu yapıyı daha farklı bir şekilde işletiyor. Haliyle aslında Context API React’ın kendi “geleneksel” yoluyla global bir state yapısı kurmak diyebiliriz.

Kod yapısını inceleyecek olursak;

store.js — Context API

Context API için oluşturduğumuz store dosyamız bu şekilde. initialState adında oluşturduğumuz sabitimiz global olarak saklayacağımız state’lerimizi ve onların ilk değerlerini temsil ediyor.

Bir alt satırda ise Context adında bir değerimiz mevcut. React’tan çağırdığımız createContext metotu ile oluşturduğumuz bu değer genel olarak tasarladığımız global yapıyı kurmamıza imkan sağlayacak. İlerleyen örneklerde de göreceğiz.

reducer.js — Context API

reducer yapısını global olarak tanımladığımız state’leri güncellemek için kurduğumuzu söylemiştik. Ekteki kodlardan anlaşıldığı üzere aslında export ettiğimiz şey “reducer” adında bir fonksiyon ve aldığı iki parametre var; state ve action.

State global olarak oluşturduğumuz state’lerin ta kendisi, action ise ilgili fonksiyonu çağırırken gönderdiğimiz parametreler. Biz eğer bir fonksiyona bir parametre göndereceksen buna action.parametreADI şeklinde erişebiliyoruz.

Mevcut örnekte dikkatinizi çekmiştir, tüm fonksiyon sistemi bir switch-case yapısı içerisinde bulunuyor. Biz istediğimiz fonksiyona ilgili case’in tipini vererek erişebiliyoruz. Aslında yazdığımız her bir fonksiyon bir switch case’i diyebiliriz. Biz, bir fonksiyon çağrısı yaparken type parametresine ilgili case kodunu yazacağız, istediğimiz güncelleme gerçekleşmiş olacak. Bu kadar kolay.

Provider.js

Son olarak oluşturduğumuz yapımız ise Provider component’imiz. Burada dikkat etmemiz gereken şey useReducer çağrısı. Bu hooks metotu Provider’a sağlayacağımız state ve dispatch değerlerini oluşturuyor ve aldığı parametreler ise state güncellemesi için yazdığımız reducer fonksiyonu ve store dosyamızda oluşturduğumuz initailState objesi. Bu fonksiyonun “state” ve “dispatch” geri dönüşü bileşenlerimizden global statelere erişmek için kullanacağımız değerlerin ta kendisi aslında. Daha sonraki örneklerde göreceğimiz context çağrısında yine bu state ve dispatch değerlerini kullanacağız.

Aslında Context yapımız şu noktada tamamlanmış oluyor. Geriye sadece Provider yapısını sarmallamak ve istediğimiz sayfadan Context bağlantısını yapmak kalıyor.

App.js — Context API

Uygulamamızın başlangıç sayfası ekte görüldüğü gibi. FirstPage ve SecondPage adını verdiğimiz componentleri birbirinden bağımsız iki sayfa olarak düşünebiliriz. Herhangi bir props paylaşımı yapmıyorlar. Haliyle ortak olarak kullandıkları hiç bir veri mevcut değil. Biz, her iki sayfadan da global state’imiz olan counter’a erişim yapacağız.

Peki nasıl?

FirstPage.js — Context API

FirstPage adlı componentimiz counter adlı global state’imizi güncellediğimiz bir sayfamız. Global yapımıza bağlanmak için iki satırlık kod bizim için yeterli. İlk olarak Context’imizi import ediyoruz ve useContext hook’u ile Provider’da sağladığımız state ve dispatch yapılarımıza erişim sağlıyoruz.

state global olarak tuttuğumuz state’lerimiz. store’da initalState adını verdiğimiz obje olarak düşünebiliriz.

dispatch ise reducer’larımıza erişmemizi sağlayan fonksiyon diyebiliriz. Aldığı parametre ise reducer’da ki action parametresine karşılık geliyor. Dikkat ettiyseniz type adında bir değer gönderiliyor, bu switch-case yapısında oluşturduğumuz fonksiyonların kodları (switch’in case’leri) ve bunu reducer dosyamızı kontrol edersek görebiliriz.

SecondPage.js — Context API

SecondPage’de ise sadece veri görüntülemesi yapacağız. Aynı şekilde Context’imizi çağırıp useContext ile bağlanıyor, state’e erişim sağlıyoruz.

Context API’nin kurulması bu kadar. Erişim için ilgili Context’e bağlanıp tüm güncellemeleri dinleyebiliyoruz. Dilerseniz bu kodun aksiyonunu da bir gözlemleyelim.

Context API

Görüldüğü üzere iki component’imiz de oluşturduğumuz context’e bağlanmış durumda ve her ikisi de counter değişikliğini gözlemleyebiliyor. FirstPage’imizden yaptığımız değişiklikler SecondPage’den takip edilebiliyor. Bu sayede artık global state’imize erişim sağlayabiliyoruz!

Redux

Gel gelelim Redux’a. Peki bu olayların hepsini biz Redux’ta nasıl yapabiliriz?

Öncelikle projemize redux ve react-redux paketlerini dahil etmemiz gerekecek. Bu paketler sayesinde context yönetimini uygulayabiliyoruz.

Önceki seferde olduğu gibi her birini uzun uzun açıklamak yerine direkt kod üzerinden ilerleyelim.

store.js — Redux

store yapımızda bu sefer sadece global olarak tutmak istediğimiz veriler mevcut. Aynı şekilde bir obje formatında tutuluyor. Bir fark yok.

reducer.js — Redux

reducer yapımız ise birebir aynı. Aynı uygulamayı yapacağımız için fonksiyonlarımız da aynı formatta, fakat yapısal olarak Context’ten hiç bir farklılığı yok.

App.js — Redux

Redux için ayırca bir Provider yazmaya gerek yok çünkü Context yapısından oluşturulmuş Provider’ı bize react-redux paketi otomatik olarak veriyor. Aslında buna benzer bir yapıyı Context’te de kurabilirdik, yani Provider dosyasını ayrı bir Provider.js olarak oluşturmaktansa direkt App.js’in içerisinde de <Context.Provider> şeklinde kullanabilirdik fakat biz o örnekte aradaki bağı ayırdık. Bu noktada kafalar karışmasın 🧠

Redux ile global state kurulumu bu kadar. Gelelim sayfalardan erişimlere..

FirstPage.js — Redux

FirstPage component’imiz bildiğiniz üzere counter’ımızı görüntüleyip güncelleme yaptığımız bir component. Context’ten farklı olarak state ve dispatch’e ayrı ayrı erişim sağlıyoruz. Bunun için react-redux paketinden useSelector ve useDispatch hooklarını kullanıyoruz. Kullanımları ise görselimizde olduğu gibi. useSelector ile istediğimiz state’e erişimi sağlıyor, useDispatch ile fonksiyon çağrısı yapacağımız nesnemizi oluşturuyoruz.

SecondPage.js — Redux

SecondPage’imiz ise bildiğiniz üzere sadece counter state’imizi görüntülediğimiz bir componentimiz. Herhangi bir dispatch işlemi mevcut değil.

Redux yapısının kurulumu bu kadar. Context API ile kuruduğumuz düzenin aynısını Redux ile kurmuş olduk. Bütün bunları bir de görsel olarak görecek olursak;

Redux

Redux ile kurduğumuz state management yapısı bir önceki örneğimizin aynısı. Herhangi bir component’tan yaptığımız değişiklik başka birinden izlenebiliyor ve takip edilebiliyor. Bu kadar basit.

Görüldüğü üzere Context API ile Redux arasında dağlar kadar bir fark söz konusu değil. Her ikisinde de temel işlemler hep aynı. Her ikisinde de store yapısı, reducer yapısı ve Provider yapısı mevcut. Farklı olan noktalar çok kafa karıştırıcı değil. Onlara ufak bir göz atmak gerekirse;

  • Context API’da Provider‘ı createContext ile oluştururken; Redux’da react-redux modülünden çağırıyoruz.
  • Context API’da componentler içinde useContext ile context nesnemize bağlanıp tüm global state’lere ve dispatch fonksiyonumuza erişirken; Redux’da useSelector ile state’lere tek tek erişiyor, useDispatch ile dispatch nesnemize erişim sağlıyoruz.
  • Context API’de oluşturduğumuz Provider nesnesine useReducer ile value değerlerini gönderirken; Redux’ta createStore ile store değerini yönlendiriyoruz.

Bunlar dışında temel olarak çok da bariz fark yok diyebiliriz. Elbette Redux’ın thunk, persist ve saga gibi farklı ihtiyaçlar için farklı varyasyonları mevcut, fakat konumuz şu an temel düzeyde ikisinin kıyaslanması.

Redux uzun süreden beri piyasada olduğu için eriştiği alanlar çok daha fazla, sektörde sıklıkça kullanılıyor. Fakat işlevsellik açısından eğer ihtiyaçlar Context API ile çözülebiliyorsa geliştiriciler Redux’a çok yanaşmıyor. Çünkü fazladan paket eklemek çok da istediğimiz bir şey değil. Üzerine bir de Redux’ın geliştiricisi Dan Abramov’un şu tweet’ini okuyunca insanın biraz gözü korkmuyor değil.

Sonuç

Biraz Türk filmi klişesi gibi olacak fakat benim gözümde Context API ile Redux iki kardeş gibiler. Özleri ve işlevleri aynı, her ikisi de “context” yapısını kullanıyor. Bu sebepten ötürü aralarında dağlar kadar bir fark yok. Redux sunduğu farklı modüllerden ötürü piyasada adını sıklıkça duyduğumuz bir paket iken, Context API aynı yapıyı daha geleneksel bir yolla kurabildiğimiz bir yaklaşım.

Hangisini tercih etmeli diye soracak olursanız;

  • Global state’leriniz çok sıklıkla değişmeyecekse,
  • Birden fazla context kullanabilecekseniz,
  • Amacınız sadece global bir state yapısı kurmak ise,

Context API tercih edin.

Eğer;

  • Global statelerinizde sık veri güncellemesi yapacaksanız, (soket dinlemesi gibi canlı veri ile çalışacaksanız)
  • Asenkron dispatch’lere ihtiyacınız olacaksa,
  • Birden fazla reducer’a ihtiyaç duyacaksanız,

Redux tercih edin.

Hangisi sizin ihtiyacınızı karşılıyorsa onu seçmeniz gerekiyor. Gördüğünüz üzere her ikisinde de aynı tasarımı kurguluyoruz. Birini öğrendikten sonra diğerini kavramak oldukça basit.

Örneklerin bulunduğu kaynak kodlar burada mevcut. Sizde localinize çekip inceleyebilirsiniz:

Redux ve Context API ile ilgili bahsedeceklerimiz şimdilik bu kadar. İkisi de sürekli adları duyulan iki kardeşler. İkisinin de birbirlerinden üstün oldukları noktalar oluyor. Ama nihai karar; sizin.

Esenlikler dilerim.

🎧

“Any fool can write code that a computer can understand. Good programmers write code that humans can understand.” — Martin Fowler

--

--