Z’den A’ya Redux — Türkçe kaynak

Derdi Redux olanın Dünya kadar Store’u olur.

Yavuz AKINCI
KoçSistem
17 min readNov 22, 2018

--

Merhaba,

Yukarıdaki görseldeki stadyum bir beyzbol stadyumudur. İlk gördüğünüzde “Redux ile beyzbolun ne alakası var” diye düşünebilirsiniz. Ancak Redux’ı öğrenirken kafamda canlanan ilk bu oldu. Aslında Türkçe yayın yapıp bu spordan örnek vermekte tereddüt ettim ama sonradan biraz araştırma yaptım. Türkiye’de beyzbol sporu çok yaygın olmamasına rağmen en çok beyzbol sopası alan ülkeler arasında 3. sıradaymışız. “Ee ne güzel, madem bu kadar bu spora ilgiliyiz neden örneklerim bunun üzerinden olmasın ki” dedim :)

Başlığın Z’den A’ya olmasının sebebi; İlk önce sadece Redux’ı beyzbola neden benzettiğimi yazacağım ardından işin derinine inip kodları ve kullanımlarını göreceğiz.

Hikaye kısmı

Bir beyzbol sahası düşünün üstünde. Oyuncular yerlerini almış, kafasındaki şapkayı düzelten bir atıcı… Yere tüküren ve topu tutmaya hazır bir oyuncu… Elindeki sopayı ayak tabanlarına vurarak toprağı temizleyen rakip oyuncunun “Hadi gönder topu gönder nasıl uzaya göndereceğim bakışı…” Biz bu ambiansın tamamına view diyelim.

Maçın başlaması için seyirciler çıldırmış durumda. Oyuncular da heyecan dorukta. Bu ambiyansı harakete geçirecek bir olayın olması lazım. Yani bir action. Bu aksiyonu atıcının topu fırlattığı an olarak düşünelim.

Topun gelişine bakılırsa vurucunun işi zor. Ancak rakip oyuncu elindeki sopayı boşuna sallamıyor. Yıllardır bu sporun içinde hatta bu spora o kadar hayran ki sopasına bir isim bile vermiş “dispatch”. Dispatch ile topa öyle bir vuruyor ki gerçekten de top neredeyse uzaya çıkacak.

Topu yakalama çalışan oyuncu tanıdık bir sima. Onun adı store. Topun gidiş hızından (middleware) veya yüksekliğinden (thunk) bilgi sahibi (logger). Topun nereye düşeceğini ve nerede tutacağını iyi biliyor.

Top yere düştü ancak daha ikinciye sekmeden store topu yakaladı. Nasıl yakalamasın ? Store da bu spora hayran ve o da eldivenine bir isim vermiş. Eldiveninin ismi reducer. Hatta bir basın toplantısında “ben reducer olmasa bu topları yakalayamam” demişliği bile var. Vuruşu yapan oyuncu koşmaya başlamış sayı olmak üzere bir an önce store topu çizgide bekleyen arkadaşlarına(components) ulaştırması lazım. Yalnız az önce sergilediği efor sonrasında o topu arkadaşlarına geri fırlatmak kolay iş değil… Neyse ki o gün sahada taraftarlar arasında karısı da (Provider) var. Karısını görünce gaza gelen store topu arkadaşına atıyor.

Store’dan çok güzel bir atış! Bugün rakibin hiç şansı yok (connect).

Redux’ın genel olarak işleyişi yukarıda bahsettiğim örnekteki gibidir. Şimdi derinlemesine işin içine girip hikâyede bahsettiğim şeylerin aslında neler olduğunu görelim.

Derin mevzular

Hikâye kısmını yine geçiyorum. Redux, Dan Abramov adında bir vatandaşın yarattığı javascript kütüphanesidir. Redux’ı React ile ayrılmaz bir bütün olarak olmazsa olmazı olarak düşünmeyin. Birlikte çok anılmalarının sebebi; birlikte popüler olmaları ve çok güzel entegre bir şekilde çalışabilmelerinden kaynaklıdır. Redux’ın ana amacı state’leri tek bir yerden yönetmektir. Application manage statement (uygulama durum yöneticisi) olarak geçer.

Redux’ı anlatıp bu görseli paylaşmadan geçmek ayıptır :).Hatırlarsanız bir önceki makalede incelediğimiz component’lerin state yönetimini hep sol taraftaki gibi yaptık. Component’imizde state tanımladık, aldık o state’i props ile child bir component’e geçtik, oradan aldık onclick tanımladık anne component’e gidip setState yaptık vs. Burada dikkat ederseniz sürekli ordan oraya bir koşuşturma içerisinde bir yapı var. Redux olmadan küçük projelerde state’lerinizi taşımak kolay görünebilir ama çok component’li uygulamalarda o state’lerin takibini sağlamanız çok sancılı olacaktır.

Store

Redux’ın çalışma mantığında store diye bir terim vardır. Bu terim aslında bir javascript objesidir. Tam karşılamasa da sanki database gibi düşünebilirsiniz. Uygulamanızda tek bir store’a sahip olup bunun içinde state’inizi tutuyorsunuz. Sizin component’leriniz bu store’u dinliyor ve storunuzda herhangi bir değişiklik olduğunda component’leriniz notify (bildirmek) oluyor. Böylelikle component’ler kendine ait ilgili güncellemeyi yapmış oluyor.

Redux data Flow

Yukarıda beyzbol olarak anlattığım hikayenin krokisi tam olarak bu şekildedir;

Kullanıcının sayfayı ya da uygulamayı açtığında karşılaştığı view kısmında bir action gerçekleştiğinde bu action’un kendi içinde bulunan payloadlar (yük) store’a dispatch edilir. Yani dispatch sayesinde store’a data taşınır ve datayı artık reducer’lara teslim eder. Action’lar gönderdikleri payload datasının sadece ne olacağını söylerler. Uygulamanın datayı nasıl değiştireceğini ve ne yapacağını bilemezler. Reducerlar bu noktada store’a gelen action’a göre uygulamanın state’inin nasıl değiştireceğini belirler ve store’a gönderir. Kısacası reducer tek işi olan veri değişimini yapar ve store’u güncellemiş olur. Ardından store bu yapılan güncellemeyi view’e yansıtır.

Reducerlar, etkisi olmayan pure (saf) fonksiyonlardır. Gelen datayı sadece değiştirip return (geri göndermek) ederler. Her uygulamanın kendine özel bir reducerı vardır.

store = {
user : userReducer,
contact : contactReducer,
product : productReducer
}
Örnek 1: reducer örneği.

Store oluşturmak

Redux bir javascript kütüphanesi olduğundan aşağıdaki şekilde projemize kurmalıyız.

npm install redux react-redux --save

Ardından store’umuzu oluşturmaya başlayabiliriz.

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import {createStore} from "redux"; // Adım 1function reducer(state,action){ // Adım 2
if(action.type === "action"){
return action.payload.user;
}
return "state";
}
const store = createStore(reducer); // Adım 3const action = { // Adım 4
type : "action",
payload : {
user : "name"
}
}
store.dispatch(action); // Adım 5
console.log(store.getState()); // Adım 6
ReactDOM.render(<App />, document.getElementById('root'));CONSOLE OUTPUT
---------------

name
Örnek 2: Redux iskeleti.

Store’unuzu oluşturacağınız alanın, en tepede ReactDom’ u render ettiğiniz alanda olması gerekiyor. Örnek 2 de gördüğünüz kod öbeği Redux’ı component’lere bağlamadan önceki yapıdır.
Adım 1 de ilk olarak Redux’ın “createStore” adındaki metodunu dahil ettik.
Adım 2 de store’umuzun ihtiyaç duyduğu reducer’ı en yalın haliyle yerleştirdik. Burada bir dipnot ekleyeceğim; Reducerlar pure fonksiyonlardır demiştim sadece return işlemi yaparlar ve gönderilen datayı güncelleyerek yerine yeni bir state dönerler. Reducer state ve action iki adet parametre alır. Burada gönderilen action’un tipine bakıyor ve koşulu sağlıyorsa action’dan gönderilen payload neyse onu basıyor. Bu şekilde store güncellenmiş oluyor.
Adım 3 de createStore’u stor adında bir değişkene tanımladık.
Adım 4 de reducera göndermek üzere basit bir action hazırladık. Burada daha önce de bahsettiğim gibi payload da ilgili data neyse onu göndermemiz gerekiyor.
Son olarak adım 5 de hazırladığımız action’u store’umuza dispatch ettik.
Adım 6 kafanızı karıştırmasın, consolea store’u yazdırırsanız; özelliklerini göreceksiniz. Burda getState diye bir özelliği var. Çıktıyı temiz bir şekilde görebilmeniz için bu şekilde console’a yazdık.

console.log(store);dispatch: ƒ dispatch(action)
getState: ƒ getState()
replaceReducer: ƒ replaceReducer(nextReducer)
subscribe: ƒ subscribe(listener)
Symbol(observable): ƒ observable()
__proto__: Object

Örnek 3: storeu consola yazdırdığımızda çıkan özellikleri.

reducer : combineReducers

Reducer’ın ne işe yaradığından bahsetmiştik. Yalnız projelerinizde sadece bir tane reducer’ınız olmayacak. Bu nedenle reducerlar’ınızı tek bir çatı altında toplamanız gerekiyor. Bu iş için combineReducers adında bir metodumuz var. Örneklerde çok büyük kod öbekleri oluşturmak istemiyorum. Bu nedenle örnek 2 deki kodda sadece reducer kısmını kaldırdım.

...
function userReducer(state ,action){
return state = "deneme isim";
}
function productReducer(state ,action){
return state = [];
}
const rootReducer = combineReducers({
user : userReducer,
product : productReducer
})
const store = createStore(rootReducer);
...
Örnek 4: combineReducers kullanımı.

Örnek 4 de ES6'nın yeni özelliği sayesinde bu şekilde de yazabilirsiniz;

...
userReducer(state = "deneme isim", action){...}
productReducer(state = [], action){...}
...
Örnek 5: kısa yazım tekniği.

4 numaralı örnekte yaptığımız şey ; İki tane reducer oluşturduk. Bu reducer’ların state’lerine değerlerine boş string diğerine boş array default değer atadık. Bu reducer’ları combineReducers özelliği ile tek çatı altında toplayarak rootReducer adında bir değişkene atadık. rootReducerı da store’un içinde tanımlayarak reducer’larımızı çalışır hale getirdik.

Provider

Geldik Redux’ın anahtar kısmına. Redux store’unuzu React uygulamanız içerisinde kullanabilmeniz için Provider nesnesi ile uygulamanızı sarmanız gerekiyor. Örnek 2 den devam ediyorum.

...
import {Provider} from "react-redux";
...
ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('root'));
...
Örnek 6: Provider kullanımı.

react-redux modülünün altında bulunan Propvider nesnesini projemize dahil diyoruz. Ardından sarmallama işlemini gerçekleştiriyoruz. Provider ile sararken store adında bir parametre tanımlamanız gerekiyor. Bu parametrenin içine yazacağınız değişken adı sizin storunuza verdiğiniz isim ile aynı olmak zorunda. Bizim store’umuzun ismi store olduğu için biz değişken adı olarak store yazdık.

Connect

React-redux altında bulunan ve component’leri store’a bağlamaya yarayan nesnedir.

...
import {Connect} from "react-redux"; //Adım 1
class DenemeComponent extend Component{
...
}
export default connect()(DenemeComponent); //Adım 2
Örnek 7: connect ile store'a bağlantı.

Adım 1 de oluşturduğumuz component’in içerisine connecti dahil ediyoruz.
Adım 2 de connect nesnesini component’imizin dışına sarıyoruz. Dikkat ettiyseniz boşta duran ayrı bir parantez var “( )”. Bu alana bir parametre yazmanız gerekiyor.

Connect parametresi mapStateToProps

mapStateToProps map’leme işlemi yaparak state’imizde o anda ne varsa onu component’iniz içerisinde props olarak kullanmamızı sağlayan bir tanımdır.

import React, { Component } from 'react';
import {connect} from "react-redux";
class App extends Component {
constructor(props) { //Adım 1
super(props);
console.log(this)
this.state = {
user : this.props.user //Adım 2
}
}

render() {
return (
<div>
{this.state.user} //Adım 3
</div>
);
}
}
const mapStateToProps = (state,props) => { //Adım 4
return state;
}
export default connect(mapStateToProps)(App); Örnek 8: component'le bağlantı kurulup store'dan props çekilmesi ve ardından state olarak kullanılması.

Adım 1 de Class oluşturulup hazır hale geldiğinde ilk anda state hazır hale gelsin istediğimiz için constructor yazdık (constructor hakkında detaylı bilgi ve örnekleri A’dan Z’ye React adlı makalemde bulabilirsiniz). Bu alandaki console.log u sadece consolda connect sonrasında nelerin geldiğini görmeniz için yazıldı. Adım 2’de Örnek 8 in altında paylaşılan görsele bakacak olursanız reducerlar ile döndüğümüz state mapStateToProps nesnesi ile bize props olarak döndü. Props’un altındaki user’ın altında this.props.user diyerek biz bu props’a ulaştık. Ardından sadece bunu bir state’e atayarak Adım 3’teki gibi ekrana bastık. Bunların hiç birini yapmadan adım 3’te sadece {this.props.user} diyerek de ekrana basabilirdik ama sadece state olarak kullanımını görmenizi istedim. Adım 4 state içerisinde ne varsa bizim props olarak kullanmamızı sağlıyor. Kendi kendinize denemeler yaparken bu örneği aynı şekilde mapStateToProps’u kullanmadan console a yazdırınca ne demek istediğimi daha detaylı anlayacaksınız.

Eğer mapStateToProps da tüm state i çekmek istemezseniz sadece
const mapStateToProps = (state, props) => ({
user: state.user
})
yazmanız yeterli.

Connect parametresi mapDispatchToProps
Uygulama durumunun değişmesine neden olabilecek eylemi göndermenize yarar. Container bileşenlerini ve eylemlerini gönderebilirsiniz. Benzer şekilde adında bir işlev tanımlayabilirsiniz. Bu kod dispatch () yöntemini alır ve sunum bileşenine enjekte etmek istediğiniz geri çağırma araçlarını döndürür. Kısacası global state üzerindeki herhangi bir değeri props’a atayabilirsiniz.

...
const mapDispatchToProps = {
onUpdateUser : updateUser,
onGetUsers : getUser
};
...
export default connect(mapStateToProps,mapDispatchToProps)(App);
Örnek 9: mapDispatchToProps kullanımı.

Connect parametresi merceProps
hangi props’un nereden geldiğini görmemizi sağlayan metottur. İçerisinde mutlaka içi boş da olsa return işlemi yapılmalıdır; (Ör return {}). 3 tane parametre alır.
-propsFormState : State’lerden gelen props’ları,
-propsFormDispatch : dispatch’den gelen props’ları,
-ownProps : component’lere geçilen parametre olarak yazılmış props’ları ifade eder.

...
const merceProps = (propsFormState, propsFormDispatch, ownProps) => {
console.log("propsFormState:", propsFormState)
console.log("propsFormDispatch:",propsFormDispatch)
console.log("ownProps:", ownProps)
return {}
}
export default connect(merceProps)(App);
Örnek 10: merceProps kullanımı.

Thunk Middleware

Yapmak istediğiniz işlemden hemen önce devreye giren, kendi içinde belli işlemlere tabi tutan ve işlemler sonunda durumuna göre en son yaptığınız işlemden devam ettirten ara katmandır. Farklı ülkelere geçişte sizin bilgilerinizi veya o ülkeye giriş için uygunluğunuzu kontrol eden bir gümrük gibi düşünün. Bu middleware kavramı sadece redux’a özel bir şey değil aslında C# gibi yazılım dillerinde de kullanılan bir kavramdır. Örneğin bir servis çağrısı yaptığınızda servise bağlanması sizin o dataya ulaşmanız vs. işlemler sırasında sizin o işlemden cevap alana kadar “sen işine bak, ben ulaşınca sana haber edeceğim” diye beklememenizi sağlar.

Asenkron bir dispatch işlemi yapıyorsanız thunk, saga, logic vb. middlewarelar kullanmanız gerekiyor. Diğerlerini incelemedim ama ben thunk kullanıyorum. Thunk’ın ana sayfasında tanımı şu şekilde “Redux thunk middleware yapısı action yerine fonksiyon dönebilmenizi sağlar” diyor. Kısacası bir action geldiğinde middleware devreye girecek. Eğer gelen action fonksiyonsa thunk çalışacak demektir.

import { compose,applyMiddleware,createStore } from "redux"; //Adım 1
import thunk from "redux-thunk"; //Adım 2
...
const allEnhancers = compose( //Adım 3
applyMiddleware(thunk),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
const store = createStore(rootReducer,allEnhancers); //Adım 4
...
Örnek 11: middleware kurulumu.

Burada yaptığımız işlem şu şekilde; Adım 1 de middleware kavramını kullanabilmek için applyMiddleware ı dahil ettik. Adım 2 de asenkron dispatch işlemlerini kontrol edebilmek için de redux-thunk ın içinde bulunan thunk ı dahil ettik. Adım 3 “AllEnhancers” adından bir değişken tanımlıyoruz. Bu bir literatül genelde bu şekilde tanımlanıyor. Compose ile middleware ve redux tool’u birleştiriyoruz. Compose adından da anlaşılacağı gibi birleştirmek için kullanılıyor. “window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()” uzun mu uzun bu kod öbeğinin tamamı Chrome’da bizim redux toll’u kullanabilmemiz için yazıldı. İlerleyen yazılarda bu kadar uzun yazmadan da nasıl kullanıldığını göstereceğim. Adım 4’te compose ettiğimiz şeyleri allEnchangers değişkeni ile çağırdık.

Yukarıdaki görsel Redux-tool’un Chrome üzerinde gösterimidir. Bu tool’u başka bir makale üzerinde anlatacağım. Şimdilik sadece state ve props değişikliklerini görebildiğimiz bir tool’dur. Şimdi konumuza dönüyoruz.

Async Action

Json servisler üzerinden get/post denemeleri yapmanız için güzel bir site var. https://jsonplaceholder.typicode.com/ bundan sonraki örneklerde bu siteden yararlanacağız.

Dispatcher’a gönderilen bir değerin, action’dan farkı henüz reducer tarafından kullanılmaya hazır olmamasıdır. Öncesinde middleware tarafından dönüştürülmelidir. Direkt kullanılmaz. Gelen değere bakılır, ona göre aksiyon alınır.

...
function getUser(){
return dispatch => {
axios.get("https://jsonplaceholder.typicode.com/users")
.then(response => console.log(response.data))
.catch(error => console.log(error));
}
}
...
Örnek 12: async dispatch örneği.

Yukarıda dediğim json sitesinden API call(api çağırmak)yapabilmek için bir fetching kütüphanesi olan axios kullandık(axios u detaylı bir önceki A’dan Z’ye React makalemde açıkladım). Call ettiğimiz datayı kullanabilmek için response ettik. Bu respons yerine başka bir değişken de yazabilirdik. Ör: .then(yavuz => console.log(yavuz.data)) gibi. Aynı sonucu verirdi. Catch datayı çekerken bir hata olması durumunda devreye girmesi ve hata mesajını gösterir.

Mutable

Javascript’te nesneler ve array’ler referans tipli veri tipleridir. Dolayısıyla siz bir reducerda bir değişiklik yapıp state return ettiğinizde referansın işaret ettiği memory’deki alanı komple değiştirmemiz gerekiyor. Javascriptte bunu yapabilmeniz için uygulamanızı Redux’da Immutable geliştirme yapmanız yapmanız gerekiyor. Mutable yapmanıza engel olunmuyor fakat her zaman Immutable kullanmanız daha sağlıklı sonuç almanızı sağlıyor.

Örneğin ekleme çıkarma işlemi olan bir reducer’ımız olsun;

...
const initialState = { //Adım 1
count : 1,
users : []
}
const reducer = (state = initialState, action) => { //Adım 2
switch (action.type) {
case "ADD":
state.count += action.payload
break;
case "SUBTRACT":
state.count -= action.payload
break;
default:
return state;
}
return state;
}
const action1 = { //Adım 3
type: "ADD",
payload: 1
};
const action2 = {
type: "ADD",
payload: 10
};
const action3 = {
type: "SUBTRACT",
payload: 5
};
store.dispatch(action1);
store.dispatch(action2);
store.dispatch(action3);
...
Örnek 13 : Mutable ekleme çıkarma işlemi örneği.

Adım 1 de bir tane initial state belirledik ve count değerini 1 olarak tanımladık. User kısmını sadece görüntü olarak koyduk, bir olayı yok :). Adım 2’de bir tane reducer oluşturduk ve dedik ki; ADD ve SUBTRACT adında iki durumum var, gelen action’lardan type’ı bu durumlardan hangisine uygunsa payload’da yazan değeri al ve benim initial state’imde belirlediğim değere ekle. Kısacası gelen type’a göre ekleme ve çıkartma işlemi. Adım 3’te 3 tane action oluşturduk ve payload’daki değerlerini store’a dispatch ettik.

Burada loglara baktığımızda ilk başta bir sıkıntı yok gibi görünsede 1. de count değerinin doğru ikincide yanlış olduğu görülüyor. İşte burada Mutable bir yapı sorununu görüyoruz. Javascript’in referans tipli çalıştığından bahsetmiştik. Burada ilk anda count 7 ye eşitlendi yani referansın işaret ettiği bellekte bulunan kısmı değiştirdik. Sonuç olarak hepsi aynı referansa eşit olduğu için count değişiyormuş gibi görünse de count aslında değişmiyor. Yapımızı projelerde bu şekilde kurgularsak kullanıcıların asla geçmiş hareketlerini göremez, sadece son yaptıklarını görebiliriz.

Peki yapıyı Immutable olarak kurgulasak nasıl olurdu birde ona bakalım.

...
const initialState = {
count: 1,
users: []
}
const reducer = (state = initialState, action) => { // Adım 1
switch (action.type) {
case "ADD":
return {
...state,
count: state.count + action.payload
}
case "SUBTRACT":
return {
...state,
count: state.count - action.payload
}
default:
return state;
}
}
const action1 = ({
type: "ADD",
payload : 1
});
const action2 = ({
type: "ADD",
payload : 10
});
const action3 = ({
type: "SUBSTRACT",
payload : 5
});
store.dispatch(action1);
store.dispatch(action2);
store.dispatch(action3);
...
Örnek 14 : Immutable ekleme çıkarma işlemi örneği.

Diğer yerler aynı olduğu için sadece adım 1’i anlatacağım. Burada ilk önce ES6'nın speard özelliğini kullanarak state’imizde o anda ne varsa alıyoruz ve neyi değiştirmek istiyorsak,

count: state.count + action.payload

o anki state’timizdeki count’u payload’da göndermiş olduğumuz değer ne ise onu eklemiş oluyoruz. Kısacası sadece state’in son değeri üzerinden işlem yapıyoruz.

Yukarıdaki Immutable örneği Object.assing ile de yapabilirdik. Ancak yazım açısından Örnek 14 deki şekil daha rahat. Seçim size kalmış.

...
const initialState = {
count: 1,
users: []
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case "ADD":
return Object.assign({}, state, {count: state.count + action.payload});
case "SUBTRACT":
return Object.assign({}, state, {count: state.count - action.payload});
default:
return state;
}
}
...
Örnek 15 : Immutable ekleme çıkarma örneği Object.assing kullanımı.

user [] kısmı boş kalmasın bir de array gönderelim immutable olarak;

...
const initialState = {
count: 1,
users: []
}
const reducer = (state = initialState ,action) =>{
switch (action.type){
case "ADD":
return {
...state,
count : state.count + action.payload,
user : [...state.users, action.payload]
}
case "SUBSTRACT" :
return {
...state,
count : state.count - action.payload,
user : [...state.users, action.payload]
}
default :
return state;
}
}
...
Örnek 16 : Immutable array e data gönderme örneği

aynı mantık ilk olarak tüm state’i çektik, ardından payload’dan gönderilen count datasını array‘e ekleyip push ettik. Kısacası tüm state’deki value değerini çek ardından action payload da bulunan value değerini ekle yeni değeri eşitle demiş olduk ve Immutable bir yapı oluşturduk.

eğer Mutable yapsaydık işaretli alanlarda sürekli [1,10,5] görünecekti.

Logger

Immutable ve Mutable örneklerinde console çıktılarını görmüşsünüzdür. Renkli; önceki state action ve sonraki state’leri gösteren bir console var. İşte bu logger sayesinde oluyor. Şu şekilde aklınızda tutabilirsiniz; sizin önceki state’inizi action’unuzu ve action sonrası aktive state’inizi browser console’una log olarak basan bir middleware’dır.

Async Action Pattern ve Promise middleware kavramı

Middleware kavramını anlamak adına çok kullanılan bir pattern hazırlayacağız. Ardından asenkron bir action işlemi yaparken bizim işimizi çok kolaylaştıran Promise kavramını öğrenip Redux makalemizi sonlandıracağız.

Yapacağımız pattern “bağlanılıyor”, ”bağlandı” ve “hata” gibi durumları içeren bir servis bağlantısı olacak. Bu durumları reducer ve action’larımız üzerinden nasıl yürüteceğimizi göreceğiz.

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
// Adım 1
import { applyMiddleware,createStore } from "redux";
import logger from "redux-logger";
import thunk from "redux-thunk";
import axios from "axios";
// Adım 2
const initialState = {
fetching : false,
fetched : false,
users : [],
error : null
}
// Adım 3
const reducer = (state = initialState, action) => {
switch (action.type) {
case "FETCH_USERS_START":
return {
...state,
fetching : true
};
case "FETCH_USERS_ERROR":
return { // Adım 11
...state,
fetching : false,
error : action.payload
};
case "RECEIVE_USER": // Adım 10
return {
...state,
fetching : false,
fetched : true,
user : action.payload
};
default :
return state;
}
}
const middleware = applyMiddleware(logger,thunk); // Adım 6const store = createStore(reducer,middleware); // Adım 7
const action = { // Adım 4
type: "FETCH_USERS_START"
}
store.dispatch(dispatch => {
dispatch(action); // Adım 5
//Adım 8 axios.get("https://jsonplaceholder.typicode.com/users")
.then(response => response.data)
.then(response => dispatch({
type : "RECEIVE_USER",
payload : response
}))
.catch (error => dispatch({ //Adım 9
type : "FETCH_USERS_ERROR",
payload : error
}))
});
ReactDOM.render(<App />, document.getElementById('root'));
Örnek 17 : Asyc Pattern iskeleti örneği.

Çok uzun bir kod öbeği oldu farkındayım ama inanın

Adım 1 : İlk olarak klasik reducer yapımızı oluşturabilmek için ilgili metotları ilgili kütüphanelerinden sayfamıza ekledik. Store için createStore, ara katman için applyMiddleware, ara katmandan geçerken state’lerimizin ve action’larımızın durumunu öğrenebilmek için logger, asyn action’ları storumuza dispatch ederken onları storumuzun anlayabileceği saf fonksiyonlara dönüştürmesi için thunk ve son olarak örnek bir json datasından data get,post..vb işlemler yapabilmek için fetching kütüphanelerinden biri olan axios u ekledik.
Adım 2 : Daha her şeyin başındayken servise bağlanma aşamasında, kısacası loading işlemindeyken görünecek boolean nesnelerini barındıran bir initialState objesi tanımladık. “fetching : false” loading işleminin bitip bitmediğini anlamak için, “fetched : false,” dataya ulaşılıp ulaşılmadığını anlamak için, “users: [],” gelen datanın tutulacağı alan (ben userları çekeceğim için ismine user dedim) ve son olarak “error : null” hatalar için error nesnesi tanımladık.
Adım 3 : Initial state’imizi içerisinde barındıran bir reducer yapısı oluşturduk. “case “FETCH_USERS_START”:” servis bağlanma işleminin kontrolünü sağlayacak, “case “FETCH_USERS_ERROR”:” hata durumunda state’i return etmesini sağlayacak ve “case “RECEIVE_USER”:” dataya ulaşılıp servis başarılı bir şekilde çalıştığında default olarak userlar dolu bir şekilde state’i dönecek ve user : []’ı dolduracak.
Adım 4 : Burada asenkron bir dispatch işlemi yapacağımız için fonksiyon oluşturduk.
Adım 5 : ilk olarak type ı “FETCH_USERS_START” olan bir action yarattık ve loading işlemini yapmış olduk. Haliyle fetching kısmını true yaptık. Öncesinde tüm state’i çekerek var olanın üzerinden devam etmiş olduk yani Immutable yapı oluşturduk.
Adım 6 : Data akışımızı kontrol etmesi için middleware ve asenkron fonksiyonlarımız için thunk’ı middleware değişkenine atadık.
Adım 7: Middleware değişkenini oluşturduğumuz storun içine yerleştirdik.
Adım 8 : Gelen datanın ilgili kısmına ulaşabilmek için datayı response ettik. “RECEIVE_USER” type’ı ile action’umuzu etiketledik datamızı payload alanına response ile yükledik.
Adım 9 : Herhangi bir hata anında hatayı yakalamak için catch kullandık.
Adım 10 : Tüm state’i çektik, datamız yerine ulaştığı için loading işlemini yani feching’i false, fetched’ı true yaparak datamızını çektiğimizi belirttik. User kısmında action dispatch ettiğimiz datayı aldık.
Adım 11 : Son olarak error kısmını catch ile buraya fırlattık.(burası sadece hata anında devreye girecektir). Reducerımızda error caseine action’umuzu aldık.

Patternimizi tamamladık ancak son olarak Promise middleware e değinerek sonlandırmak istiyorum. Promise’in yaptığı iş şudur;

...
const action = {
type: "FETCH_USERS_START"
}
store.dispatch(dispatch => {
dispatch(action);
axios.get("https://jsonplaceholder.typicode.com/users")
.then(response => response.data)
.then(response => dispatch({
type : "RECEIVE_USER",
payload : response
}))
.catch (error => dispatch({ //Adım 9
type : "FETCH_USERS_ERROR",
payload : error
}))
});
...

Yukarıdaki büyük kod kalabalığı yerine;

...
store.dispatch({
type : "FETCH_USERS_PENDING",
payload : axios.get("https://jsonplaceholder.typicode.com/users").then(response => response.data)
});
...

Sadece bu kadarlık kod yazmanız yeterli. Tabi öncesinde npm ile redux-promise-middleware’i kurmanız ve applyMiddleware(reduxPromiseMiddleware(),thunk,…vb) şeklinde middlewareinize dahil etmeniz gerekiyor. Promise in kendine ait PENNDING,FULFILLED ve REJECT olarak tipleri var. Bu nedenle reducerdaki type isimlerini Promise’e göre değiştirmeniz gerekiyor. Ör :”FETCH_USERS_START” yerine “FETCH_USERS_PENDING” gibi. Daha detaylı incelemek için bu adrese bakabilirsiniz. Promise Link.

React ve Redux makalelerimizin sonuna geldik. Umarım yardımcı olabilmişimdir. Elimden geldiğince her konuya değinmeye çalıştım. Dediğim gibi bu makaleler sadece el kitabı olması amacıyla yapıldı. Olur da bir gün proje yaparken “bu axios neydi” ya da “Store kurarken ne tanımlayıp nereye ne atıyorduk” gibi takıldığınız noktalarda bakıp bu makalelerden yararlanırsanız ve projenize bir katkıda bulunabilirsem ne mutlu bana. Geldik iş görüşmesi sırasında süper bilgili arkadaşların sorabilme ihtimallerinin olduğu tanımlara.

Kavramlar

import {compose} from “redux”;
import {applyMiddleware} from “redux”;
import thunk from “redux-thunk”;
import logger from “redux-logger”;
import reduxPromiseMiddleware from “redux-promise-middleware”;

View
Kullanıcının sayfayı ya da uygulamayı açtığında karşılaştığı kısımdır.

store.dispatch()
Action’daki datanın store’a taşınmasıdır.

const action
State’i değiştirmek için kullanılan datayı taşır, uygulamanın state’inin nasıl değiştirileceğini bilemezler.

function reducer()
Store’a gelen action sonucunda uygulamanın state’inin nasıl değiştirileceğini belirler. Veri değişimi tek işidir.

combineReducers({})
Reducerlarınızı tek çatı altında toplar.

<Provider store={store}>
Storu uygulama içerisinde kullanılır hale getirebilmek için bir nesnedir.

connect()()
Store’u component’e bağlamak için kullanılan nesnedir. Provider’a ihtiyaç duyar.

mapStateToProps
Mapleme işlemi yaparak state’imizde o anda ne varsa onu component’iniz içerisinde props olarak kullanmamızı sağlayan bir tanımdır

mapDispatchToProps
Uygulama durumunun değişmesine neden olabilecek eylemi göndermenize yarar.

mergeProps = (propsFormState, propsFormDispatch, ownProps) Hangi props’un nereden geldiğini görmemizi sağlayan methottur. içerisinde mutlaka boş da olsan return işlemi yapılmalıdır (Ör: return {})

1)propsFormState
State’lerden gelen props’ları ifade eder.

2)propsFormDispatch
Dispatch’den gelen props’ları ifade eder.

3)ownProps
Component’lere geçilen parametre olarak yazılmış props’ları ifade eder.

middleware
Dispatch işlemlerinde action’ları süzgecinden geçiren ara katman.

applyMiddleware(thunk)
Thunk asenkron dispatch işlemlerini kontrol etmemizi sağlayan middleware türü. Fonksiyonları saf hale dönüştürür.

compose
Store un içine özellik tanımlayacağımızda tüm özellikleri tek bir çatı altında tanımlayıp değişken olarak atamamızı sağlar.

combineReducers({})
Reducerları bir araya toplayan metottur.

catch
Axios ile kullanılan bir metottur; gelen datada hatalı bir işlem olursa devreye girer.

logger
Sizin önceki state’inizi action’unuzu ve action sonrası aktive state’inizi browser consoluna log olarak basan bir middlewaredır.

promise middleware
Rutin vaat üretme async görevleri için kullanılır. Siz girişi action’da girişi verirsiniz o tüm aşamaları kendi yerine getirir.

Soru ve önerileriniz için :
linkedin.com/in/yavuzakinci Linkedin’den ya da
yavuzakinci.com adresinde iletişim sayfasından bana ulaşabilirsiniz.

--

--