Karikatürler üzerinden Redux

Kerem Sevencan
11 min readApr 5, 2018

Orjinal makaleyi aşağıdaki linkten ingilizce olarak okuyabilirsiniz. ⤵️

Gereksiz Not: Bu yazıyı 15 mayıs 2017 yılında tamamlamış, paylaşma öncesi fikirlerini aldığım insanların yorumları üzerine vakit buldukça düzenlemeler yapıyordum. Düzenlemeleri yaparken bir türlü tatminkar bir sonuç elde edip, bu yazıyı paylaşamadım. Gelecek yazılarımda çeviri yapmaya çabalamak yerine Gulp yazısında olduğu gibi kendi yazım tarzım ile devam edeceğim.

Okumadıysanız, önce Flux çevirisini okumanızı öneririm. Bu makale o çevirinin içerisinden detaylar içermekte ve devamı niteliğinde.

Bu yazım içerisinde çeviriye kendimi kaptırdığım için eğlenceli bir dile geçiş yapamadım, sadece yazının akışını bozmadan detaylar ekleyebildim. Çeviri birebir değildir, benim eyyorlamalarım mevcut.

Flux pattern kafa karıştıran bir hiyerarşiye sahip, şimdi kafanızı daha çok karıştıracak bir konuya geçiş yapacağız; Flux ile Redux arasındaki fark. Bu makalenin amacı hem Redux nedir? buna cevap vermiş olmak hem de Flux’tan ne gibi farklılıkları var bunlara değinmiş olmak.

Neden Redux?

Flux hiyerarşisini kullanan en popüler konsept diyebiliriz. Redux, Flux ile aynı problemleri çözmekte, sadece birkaç artıya sahip.

Tıpkı Flux gibi, uygulamanızın state(veri) değişikliklerini öngörülebilir hale getirmeyi amaçlıyor. Predictable State Container.

Bu 3 kelimenin yan yana gelmesinden korkmayın, başına “Ponçik” falan ekleyin, sevmeye çalışın. Redux’ı anladığınız zaman bu 3 kelime size çok anlamlı gelecek.

Uygulama State’ini değiştirmek için bir eylem yani action olması gerekli. Bu eyleme örnek vermek gerekirse, “todoları yükle”, “todo ekle” gibi kullanıcının tıklamalar veya seçimler yaparak başlatabileceği eylemler düşünebilirsiniz.

State’i bir action olmadan yani doğrudan değiştirmenin bir yolu yok. State’i barındıran Store öğesi sadece getter methoduna sahip. setter method’u yok. Flux bu temel yapı konusunda Redux ile çok benzeşiyor.

Peki, neden yeni bir hiyerarşi? Redux’ın yaratıcısı Dan Abramov, Flux’ı geliştirmek için bir fırsat gördü. O daha iyi geliştirici araçları istiyordu, Flux’taki öngörülebilirliği bozmadan birkaç şeyi değiştirerek, bu daha iyi geliştirici araçları geliştirme isteğini hayata geçirebileceğini gördü.

Geliştirmek istediği araçlar hot reloading ve time travel debugging idi. (şu an çevirisine başladığım bir başka cartoon yazısında mevcut kendisi: cartoon yazısı). Fakat Flux ile bu yapmak istediği araçları geliştirme kısmında zorlandı. Bu yüzden Redux’ı yarattı.

Problem 1: Store öğesini güncellemek istediğinizde state’i kayboluyor

Flux içerisinde Store iki şey içeriyor.

  1. State değiştirme mantığı(logic)
  2. Mevcut state’in kendisi

Bu ikisini aynı obje içerisinde tutmak hot reloading için bir problemdi. Store içerisinde state değiştirme mantığını güncellediğinizde, Store içerisinde tutulan state’i kaybedersiniz. Buna ek olarak, Store’un bu mantık güncellenmesi sırasında sistemin kalan diğer parçalarına olan bağlılıkları kaybolmakta.

Çözüm

Store’un içerdiği iki şeyi ayırmak.

İlk olarak state’i tutan bir obje yaratalım. Bu obje yeniden yüklenmeyecek. Tüm state değişiklik logiclerini içeren başka bir obje yaratalım. Bu obje yeniden yüklenecek ve bu yeniden yüklenme sırasında state’i kaybetme gibi bir derdi olmayacak.

Problem 2: Her yeni action ile State yeniden yazılıyor

Time Travel Debugging içerisinde, state objesinin her değişiklik ile oluşan versiyonları takip edilebilir olmalı. Bu sayede, önceki bir state’e geri dönüş sağlayabilirsin.

Herbir state değişikliğinde, önceki state’i birprevious state objects array’ine eklemelisin. JavaScript’in çalışma şekli nedeniyle, değişkeni bir array’e eklemek bir işe yaramaz. Bu objenin bir snapshot’ını yaratmaz, sadece aynı objenin yeni bir işaretçisini(pointer) yaratır.

Yeni bir işaretçisini yaratmak denilince anlamlı gelmemiş olabilir.
Şöyle bir link var, burada anlatılmak istenen kısaca JavaScript içerisinde bir variable declaration yaptığınızda variable’a değer olarak bir obje veya array verirseniz, onun bir referansını alır. Yeni yarattığınız variable içerisinde obje mevcut fakat objenin bir propertysini değiştirdiğinizde referans aldığınız obje dahi değişir.

Bu önceki state’i array’e ekleme işleminin çalışmasını sağlamak ve yanlışlıkla geçmiş sürümleri değiştirmemeniz için her yeni state objesinin tamamen ayrı bir obje olması gerekli.

Çözüm

Store’a bir action geldiği anda, bu action’ın istediği değişikliği yerine getirmek için state objesinin kendisini değiştirmeyin. Bunun yerine, state objesini kopyalayın ve kopyası üzerinde değişikliği gerçekleştirin.

Problem 3: third-party pluginler geliştirilmesi için güzel bir alan yok

Geliştirici araçları geliştirirken, onları jenerik bir biçimde yazabilmen gerekiyor. Bir kullanıcı kendi kodunu uyarlama çabasına girmeden yalnızca geliştirdiği aracı sistemin içine bırakabilmeli.

Bu yukarıdaki gerekliliklerin gerçekleşmesi için, kodun eklediği şeyleri beklediği uzantı noktalarına ihtiyaç duyarsınız.

Bir örnek olarak loglamak.

Diyelim ki, her bir eylem geldiğinde eylem geldiğini console.log() yaparak console’a yazmak istiyorsunuz ve sonrasında bu eylem sonunda değişen state’i console.log() yazarak yazdıracaksınız. Flux’ta hem dispatcher’ın hem de her Store öğesinin güncellemelerine abone olmalısın.

Ancak bu third-party pluginler için yazılmış özel kodun kolayca yapabileceği birşey değil.

Çözüm
Sistemin diğer objelerini sarmasını kolaylaştırın. Bu diğer objeler kendi fonksiyonalitesini orjinal’in üzerine ekler. Bu tür uzantı noktalarını “enchancers” veya “higher order” objelerinde veya ayrıca middleware gibi şeylerde görebilirsiniz.

Buna ek olarak, state değiştirme logiclerini yapılandırmak için bir ağaç yapısı kullanın. Bu ağaç yapısı sayesinde Store sadece bir defa “state değişti” eventi yayar.

Bu “state değişti” eventi, eyleminden gelen değişiklik isteği tüm stateler yani bütün state tree’den geçerek istenen değişiklikler işlendikten sonra oluşur.

Not: Bu sorunlar ve çözümler ile, geliştirici araçlarının kullanılma caselerine odaklanıyorum. Bu değişiklikler diğer kullanım alanlarına da yardımcı olur. Bunun üzerine, Redux ve Flux arasında başka farklılıklar var. Örneğin, Redux ayrıca boilerplateleri azaltır ve storeların içerisindeki logicleri tekrar kullanmayı kolaylaştırır. İşte Redux için diğer farklılıkların bir listesi upsides to Redux.

Öyleyse Redux’ın bunları mümkün kıldığını bir görelim!

Yeni karakter kadrosu

Redux’ın Karakter kadrosu Flux’tan biraz farklı.

Action creators

Redux, Flux’ın action creator yapısını neredeyse birebir korumakta. Ne zaman uygulama içerisinde bir state değişikliği yapmak isterseniz, bir action yaratırsınız. State’in değiştirilmesi için tek yol budur.

Flux makalesinde söylediğim gibi, action creator karakterinin bir telgraf operatörü olduğunu düşünüyorum. Temel olarak nasıl bir mesaj göndermek istediğinizi bilerek action creator’a gidersiniz ve action creator bunu sistemin geri kalanının anlayabileceği şekilde biçimlendirir.

Flux’a göre tek farklı nokta; Redux içerisinde action creator, action’ı dispatcher’a göndermez. Bunun yerine, biçimlendirilmiş bir action objesi döndürür.

The store

Flux içerisinde Store karakterini aşırı kontrol bağımlısı bürokratlar olarak tanımlamıştım. Tüm state değişiklikleri kişisel olarak bir store tarafından yapılmalı ve doğrudan talep edilmek yerine action ile başlayan süreçten geçmeli. Redux içerisinde Store karakteri halen daha kontrolcü ve bürokratik, ancak biraz daha farklı.

Flux’ta birden fazla store’a sahip olabilirsin. Her store’un kendine ait küçük bir beyliği, kontrolü elinde tutan yönetimi var. Store state’in küçük bir dilimini elinde tutar ve bu küçük state dilimi üzerindeki değişiklik mantıklarına sahip.

Redux içerisinde store karakterine daha fazla sorumluluk devredilmiş durumda.

Redux içerisinde yalnızca bir store var… bu yüzden her şeyi kendisi yaparsa, çok fazla iş yapmış olurdu.

Bunun yerine, bütün state tree’yi elinde tutmakla ilgilenir. Daha sonra, state değişikliklerinin neye ihtiyacı olduğunu bulmak için çalışmayı başkasına devreder. Bu çalışmayı devralan kişi Root reducer. Root reducer, bu çalışma için Reducerları yönlendirir.

Redux akışı içerisinde Dispatcher olmadığını fark etmiş olabilirsiniz. Bunun nedeni, bu güç kapma olaylarında Store karakteri dispatching görevini devraldı.

The reducers

Store, ne zaman bir action’ın state’i değiştirdiğini bilmek isterse, Root reducer’a sorar. Root reducer bu görevi alır ve state objesinin keylerine göre tüm state’i böler. State’in bölünmüş olan her bir dilimi, o dilim ile nasıl başa çıkacağını bilen bir reducer’a aktarılır.

Reducerları fotokopi konusunda biraz aşırı tutkulu olan beyaz yakalı bir işçi birimi olarak düşünüyorum. Hiçbir şeyi karıştırmak istemezler, bu yüzden onlara aktarılan state’i değiştirmezler. Bunun yerine, bir kopyasını oluşturur ve tüm değişikleri o kopya üzerinde yaparlar.

Bu Redux’ın ana fikirlerinden bir tanesi. State objesi doğrudan manipule edilmez.

Bunun yerine, state objesinin her bir dilimi kopyalanır ve değişiklikler yapılarak yeni bir state objesi içerisinde bu dilimlerin hepsi birleştirilir.

Güncel bir state objesi oluşturmak için Reducerlar kendi kopyalarını Root Reducer’a geri gönderirler ve Root Reducer bu kopyaları birleştirir. Bu işlemin ardından Root Reducer yeni state objesini Store’a geri gönderir ve store yeni state’i oluşturur.

Küçük bir uygulama geliştiriyorsanız, tüm state objesinin kopyasını çıkarıp, değişiklikleri yapan sadece bir reducer yeterli olabilir.

Geniş bir uygulama geliştiriyorsanız, bir Root Reducer’a bağlı birçok alt reducer’dan oluşan Reducer Tree tasarlayabilirsiniz.

Bu detay, Flux ve Redux arasındaki bir başka farktır. Flux içerisinde Store’lar illa birbirine bağlı olmak zorunda değiller ve düz bir yapıya sahipler. Redux’ta ise reducerlar bir hiyerarşi içindedirler. Bu hiyerarşi, component hiyerarşisinde olduğu gibi gerekli derinlik seviyesi kadar seviyeye inebilir.

View: smart ve dumb componentler

Flux içerisinde Controller ve Regular view adında iki farklı view yapısı mevcut. Controller Viewlar orta yöneticiler gibi davranır, bu viewlar kendi altındaki viewlar ve store arasındaki ilişkiyi yönetir.

In Redux, there’s a similar concept: smart and dumb components. The smart components are the managers. They follow a few more rules than the controller views, though:

Redux içerisinde benzer bir konsept mevcut: Smart ve Dumb components. Smart componentler yöneticilerdir. Controller Viewlara göre birkaç kural daha fazla uygulamaktalar:

  • Smart componentler actionlardan sorumludur. Eğer bu componentin altında barınan bir dumb component bir action fırlatmak isterse, üstündeki Smart component propslar aracılığılıyla dumb component’e bir function teslim eder. Böylece dumb component bu proplar aracılığı ile teslim edilen function’ı bir callback olarak kullanabilir.
  • Smart componentlerin kendi CSS stilleri yoktur.
  • Smart componentler nadiren kendi DOM’larını emit ederler. Bunun yerine, DOM öğelerini düzenlemesi için Dumb componentleri kullanırlar.

Dumb components don’t depend on action files directly, since all actions are passed in via props. This means that the dumb component can be reused in a different app that has different logic. They also contain the styles that they need to look reasonably good (though you can allow for custom styling — just accept a style prop and merge it in to the default styles).

Dump componentler action dosyalarına doğrudan bağlı değiller, çünkü tüm actionlar prop’lar üzerinden iletilir. Bu sayede; dumb component uygulamanız içerisinde farklı bir mantığa sahip bir alanda yeniden kullanılabilir olacaktır.

Ayrıca makul derecede iyi görünmeleri için ihtiyaç duydukları stilleri de içerirler (ancak özel stillendirme için izin vermelisiniz — sadece bir style prop’u kabul edin ve varsayılan stiller ile birleştirin).

View layer binding

Viewları bir store’a bağlamak yani bir View’ı smart component’e çevirmek için Redux’ın biraz yardıma ihtiyacı var. İkisini birbirine bağlamak için bir şeye ihtiyacı var. Bu şeye; view layer binding deniyor. React kullanıyorsanız, bu şey; react-redux.

View layer binding, view tree için IT departmanı gibi. Tüm componentlerin store’a bağlanabilmesini sağlar.

Ayrıca hiyerarşinin geri kalanının bu componentleri anlaması zorundalığını ortadan kaldırmak için çok fazla teknik detaya dikkat eder.

View layer binding üç farklı konsept ortaya koymakta:

  1. Provider component: Bu component tree’nin etrafına sarılır, wrap edilir. Bu component kendi altındaki componentlerin connect() methodunu kullanarak store’a bağlanmalarını kolaylaştırır.
  2. connect(): bu react-redux tarafından sağlanan bir method. Eğer bir component state güncellemelerini almak isterse, connect() method’u ile kendini sarabilir yani wrap edebilir. Ardından, connect methodu selector kullanarak tüm kablolama yani bağlantıyı yapacak.
  3. selector: Bu sizin yazabileceğiniz bir function. Component’in uygulama state’i içerisindeki ihtiyaç duyduğu kısımları belirtirler.

Root component

Tüm React uygulamarının root component’i vardır. Bu componentin tek ayrıcalığı, component hiyerarşisinin en üst katmanında durması. Fakat Flux’a göre Redux uygulamarında, bu component daha fazla sorumluluk alır.

C-level seviyesinde bir yönetici gibi davranır. Bir işi çözmek için tüm ekipleri olması gereken yere konumlar. Hangi reducer(lar)ı kullanacağını söyleyerek Store’u yaratır. Ardından layer binding view ve viewları bir araya getirir.

Ancak, uygulamayı başlattıktan sonra root component sorumluluklarını bırakır. rerender yani tekrar render etme işlemini tetiklemek gibi gündelik endişelere kapılmaz. Bu endişeyi, view layer binding yardımıyla altındaki componentlere devreder.

Birlikte nasıl çalışıyorlar

Bu parçaların işleyen bir uygulama oluşturmak için birlikte nasıl çalıştığını görelim.

Kurulum

Uygulamanın farklı bölümlerinin birlikte kablolanması gerekir. Bu sadece kurulum sırasında yapılır.

  1. Store’u hazır hale getirin. Root component createStore() fonksiyonunu kullanarak store’u yaratır ve ona hangi Root reducer’ı kullanması gerektiğini söyler. Bu Root reducer’ın halihazırdan kendisine rapor veren bir reducer ekibi var. Root reducer, combineReducers() kullanarak reducerlardan bir ekip kurdu.

2. Store ve componentler arasındaki iletişimi kurun. Root component alt componentlerini Provider component ile sarmalar ve store ile provider arasındaki bağlantıyı kurar.

Provider, temel olarak componentleri güncelllemek için bir ağ oluşturur. Smart componentler connect() methodunu kullanarak bu ağa bağlanırlar. Bu bağlantı state güncellemelerini almalarını garanti eder.

3. Callback action’ları hazırlayın. Dumb componentlerin actionlarla çalışmalarını kolaylaştırmak için smart componentler bindActionCreators() fonksiyonunu kullanarak action callbackleri oluşturabilirler. Bu şekilde, dumb componentlere bir callback iletebilirler. Action, biçimlendirme işi tamamlandığında otomatik olarak dispatch edilecektir.

Veri akışı

Uygulama kurulduğuna göre, kullanıcı onunla etkileşime başlamış olabilir. Veri akışını görmek için bir action tetikleyelim.

  1. View bir action istiyor. Action creator onu biçimlendirir ve döndürür.

2. Action otomatik olarak gönderilir (eğer kurulum aşamasında bindActionCreators() fonksiyonu kullanıldıysa) veya view action’ı dispatch eder.

3. Store action’ı alır. Mevcut state tree’yi ve action’ı root reducer’a gönderir.

4. Root reducer state tree’yi dilimlere ayırır. Daha sonra her dilimi kendisiyle nasıl başa çıkacağını bilen alt reducer’a aktarır.

5. Alt reducer dilimi kopyalar ve kopya üzerinde değişiklikleri yapar. Dilimin kopyasını root reducer’a geri gönderir.

6. Tüm alt reducerlar kendi dilimlerinin kopyalarını döndürdükten sonra, root reducer hepsini bir araya getirererek güncellenmiş state tree’nin tamamını Store’a geri gönderir. Store eski state tree yerine yenisini koyar.

7. Store, view layer binding’e elinde yeni bir state olduğunu söyler.

8. View layer binding store’dan yeni state’i ona göndermesini ister.

9. View layer binding bir rerender işlemi tetikler.

İşte benim Redux’ı ve Flux ile olan farklılıklarını nasıl düşündüğüm. Umarım yardımcı olur!

Umarım bu yazı biraz olsun Redux’ı anlamanıza yardımcı olabilmiştir. Birkaç etkinlikte dilim döndüğünce interaktif bir şekilde Redux’ı anlatmayı denedim, ilerleyen zamanlarda Redux’ı kendi yazım tarzım ile aktarmayı denemeyi düşünüyorum, umarım başarabilirim :)

Okuyan, destek olan, paylaşan herkese çok teşekkür ederim 🎉

E-posta veya twitter üzerinden arayüz geliştirme üzerine sorularınız varsa bildiğim kadarıyla cevaplamaya çalışırım. Ayrıca bu çeviriyi yaparken bana destek olan Oğuz Kılıç ve Cem Arguvanlı’ya burada teşekkürlerimi gönderiyorum.

--

--