Redux'a Giriş - Basit fakat komple bir örnek
Angular.JS ile uzun zaman boyunca çalıştıktan sonra şirket değiştirince React.JS'e geçiş yapmak zorunda kaldım. Pek çok kişi React öğrenme sürecinin Angular öğrenme sürecinden çok daha hızlı olduğunu söylese de işler benim için o şekilde işlemiyordu. Angular'da directives, services, controllers, watchers vs. öğrenilecek pek çok kavram olsa da; Angular tek bir paket içerisinde çok daha kompakt ve tutarlı olduğu için bence anlaşılması daha kolaydı. Evet React öğrenmesi daha kolay olabilir ancak tek başına yeterli bir kütüphane değil. Veri modeliniz için flux ya da redux benzeri kütüphaneler kullanmak gerekiyor. Daha sonra en azından thunk, router kütüphanelerini de eklemek ve öğrenmek gerekiyor. JSX denilen garip görünümlü bir kütüphane var. Bu kütüphane o kadar garip ki tarayıcılar algılayamıyor. Bu sebeple Babel, Webpack vs. pek çok ayrıntı ile uğraşmanız gerekiyor. Angular için çok ayrıntılı ve öğrenme süreci çok uzun diyenler nedense React'ın yancıları hakkında yorum yapmıyorlar. Neyse konumuz Angular vs React ya da hangisinin daha iyi olduğu değil.
Konumuz Redux. Bence React öğrenme sürecindeki en önemli yapı. Aslında Redux'ın React ile bir alakası yok. Birbirinden bağımsız iki kütüphane. Facebook, React'ı ilk geliştirirken Flux denen mimariyi kuruyor. Dan Abramov denen şahıs bunu pek beğenmiyor ve kendi kütüphanesini buna alternatif olarak geliştiriyor. Bu, Zuckerberg'in ilgisini çekiyor ve Facebook'ta Dan Abramov'a önemli bir konum veriyor. Neyse bunlar işin magazin kısmı. React için Redux'a, Redux için de React'a gerek yok. Ancak bu ikili şu an web geliştirme platformunun yıldızı. Bu sebeple React'a başlamadan önce Redux'ı anlamak önemli. Bu makale Redux'a giriş yapmış ancak kafasında bazı şeyleri oturtamamış kişilere (aslında bana) yardımcı olmak amacıyla yazıldı. O yüzden Redux ile ilgili en azından Dan Abramov'un temel örneklerine göz attığınızı varsayıyorum.
Aslında Dan Abramov, https://egghead.io/courses/getting-started-with-redux adresinde birinci ağızdan, gereksiz ayrıntılara girmeden tane tane anlatıyor. Ancak verilen örnekler yalnızca tek bir bileşen ya da nesne için tasarlanmış. Bu videoları izlerken ikinci veya üçüncü bir örneği nasıl eklemem gerektiği kafama pek yatmadı. İnternet'te bulduğum örnekler ise oldukça ayrıntılı idi. Ayrıca bu örneklerin çoğu çok da aşina olmadığım ES2015 ile yazılmış olduğu için pek çok konuda bağlantı kuramadım. Öyle bir örnek aradım ki Dan Abramov'un örneklerinden biraz geniş olsun ama İnternet'teki diğer örneklere göre de biraz basit olsun. Tüm bunları da Vanilla Script syntax'ı ile yapsın. Böyle bir örnek bulamadım bu sebeple kendim yazmaya karar verdim. Örneği de sizinle paylaşmak istedim.
İnternet'te REDUX ile ilgili pek çok örnek var ancak Dan Abramov'un örnekleri de dahil olmak üzere pek çoğu ya çok basit ya da çok ayrıntılı. Mesela TODO örneği. Tek bir "state" üzerinden oldukça basit bir şekilde anlatılmış. Diğer örnekler ise oldukça ayrıntlı olduğu için REDUX'ı kafamda tam anlamıyla oturtmam uzun bir zaman aldı. Tek bir reducer örneği ile 2 reducer örneği arasındaki fark, 2 reducer örneği ile 100 reducer örneği arasındaki farktan kat be kat fazla. Bu sebeple birden fazla state'in olduğu basit bir örneğin başlangıç aşamasında çok daha yardımcı olacağını düşünüyorum.
Ayrıca Babel vs. uğraşmadan direk tarayıcıda çalışıp debug edilebilecek Vanilla JS kodunun başlangıç aşamasında çok daha yardımcı olabileceğini düşündüm. Bu sebeple bu örneği öncelikle "kendime notlar" şeklinde hazırladım. Ancak başkalarına da yardımcı olursa ne mutlu bana.
Örnek tek bir html sayfasından ve içerisine gömülü olan script'ten oluşuyor. Elbette gerçek bir uygulamada tüm yapıları birbirinden ayırmanız ve mümkünse de ES2015, WebPack ve Babel kullanmanız gerekir. Ancak Vanilla JS, öğrenme sürecinde daha basit olduğu için daha uygun.
Action (4 tane)
Action creator (4 tane)
Reducer (2 tane)
Initial State
Root Reducer
Dispatch Action
gibi REDUX'ın temel bileşenlerini olabilecek en basit şekilde anlatmaya çalıştım. redux-thunk örneği ile birleştirilmesini ise gelecek bir zamanda yüklemeye çalışacağım.
Örneğe direk olarak aşağıdaki adresten ulaşabilirsiniz:
https://s3.us-east-2.amazonaws.com/cuneyt-aliustaoglu/blog.tr/ReduxBasitveKompleOrnek/index.html
Örneğimizde 2 temel nesne var.
- Metin kutusu
- Düğme
Metin kutusuna ait biri metin içeriği diğeri ise rengi olmak üzere 2 action'ımız var. Düğme için ise düğme text değeri ve düğmenin aktifliği olmak üzere yine 2 tane action'umuz var. Bu örnekteki 2 nesne yerine 100 nesneniz de olsa yapı aynı kalacak.
Action creator
Metin kutusu ve düğmenin state'ini değiştirecek action'lar.
// Metin ile ilgili action'lar
var actionMetinIcerik = function(icerik) {
return {
type: "METIN.ICERIK",
icerik: icerik
}
}
var actionMetinRenk = function(renk) {
return {
type: "METIN.RENK",
renk: renk
}
}
// Düğme ile ilgili action'lar
var actionDugmeDeger = function(deger) {
return {
type: "DUGME.DEGER",
deger: deger
}
}
var actionDugmeAktiflik = function(aktiflik) {
return {
type: "DUGME.AKTIFLIK",
aktiflik: aktiflik
}
}
}
Reducer
// Metin ile ilgili reducer (yalnızca metinle ilgili action'larla ilgilenir)
var reducerMetin = function (state, action){
if (typeof state === 'undefined') {
return null;
}
switch(action.type) {
case "METIN.ICERIK":
return Object.assign({}, state, { icerik : action.icerik });
case "METIN.RENK":
return Object.assign({}, state, { renk : action.renk });
default:
return state;
}
}
// Düğme ile ilgili reducer (yalnızca düğme ile ilgili action'larla ilgilenir)
var reducerDugme = function (state, action){
if (typeof state === 'undefined') {
return null;
}
switch(action.type) {
case "DUGME.DEGER":
return Object.assign({}, state, { deger : action.deger });
case "DUGME.AKTIFLIK":
return Object.assign({}, state, { aktiflik : action.aktiflik });
default:
return state;
}
}
Initial State
var initialState = {
reducerMetin: {
icerik: "Metin",
renk: "White"
},
reducerDugme: {
deger: "Dugme",
aktiflik: true
}
}
Root reducer
// Bütün reducerları birleştir (her yeni yapı için yeni reduclerlar oluşturun)
var rootReducer = Redux.combineReducers({
reducerMetin: reducerMetin,
reducerDugme: reducerDugme
});
Create Store
// rootReducer'ın olduğu store'umuzu oluşturalım
var store = Redux.createStore(rootReducer, initialState);
Listeners
// Listeners
var renderMetin = function(){
$('#txtMetin').val(store.getState().reducerMetin.icerik);
$("#txtMetin").css('background-color', store.getState().reducerMetin.renk);
}
var renderDugme = function(){
$('#btnDugme').val(store.getState().reducerDugme.deger);
$('#btnDugme').prop('disabled', !store.getState().reducerDugme.aktiflik);
}
Subscribers
// Listeners için Subscribers
store.subscribe(renderMetin);
store.subscribe(renderDugme);
Klasik JS fonksiyonlarimiz
// Klasik HTML/JS fonksiyonlarımız
var btnMetinIcerikOnClick = function(){
var act = actionMetinIcerik($("#txtMetinIcerik").val());
store.dispatch(act);
}
var btnMetinRenkOnClick = function(){
var act = actionMetinRenk($("#txtMetinRenk").val());
store.dispatch(act);
}
var btnDugmeDegerOnClick = function(){
var act = actionDugmeDeger($("#txtDugmeDeger").val());
store.dispatch(act);
}
var btnDugmeAktiflikOnClick = function(){
var act = actionDugmeAktiflik($("#chkDugmeAktiflik").prop('checked'));
store.dispatch(act);
}
Oldukça basit ve sade bir dizayn olduğu için kod kendisini zaten anlatıyor. Ancak Redux konusunda az da olsa deneyiminiz olduğunu varsayıyorum. Gelecek makalede thunk kullanarak modelimizi asenkron olarak güncelleyeceğiz.