React Native Instagram — Redux Kullanımı
Aslen 8 yazılık bu serinin diğer yazılarına aşağıdaki linklerden ulaşabilirsiniz:
- Proje mimarisi
- Projenin oluşturulması ve React Native Navigation
- BottomBar’ın oluşturulması
- FlatList kullanımı (Post’ların görüntülenmesi)
- Story Listesi
- RNCamera kullanımı
- React Navigation Drawer kullanımı
- Redux kullanımı
Önceki yazımda react-navigation-drawer’ı kodlayıp, bottomBar’daki tüm ekranları mock olarak tamamlamıştık. Şimdi bu yazıda redux kullanarak API isteklerinin yapılmasını sağlayabiliriz. Projede kaldığımız yerden devam etmek için aşağıdaki 6-adding-drawer branch’ine gidebilirsiniz:
Redux kullanımı konusunda daha önce Devnot’ta bir yazı yazmıştım, oradan temel bilgileri edinebilirsiniz. Öncelikle redux ve diğer kütüphaneleri indirerek projeye dahil edelim:
npm i redux --save
npm i react-redux --save
npm i redux-logger --save
npm i redux-persist --save
npm i redux-saga --save
npm i isomorphic-fetch --save
npm i normalizr --save
npm i lodash --save
Paketleri çıklayacak olursak, redux bildiğimiz redux kütüphanesi, react-redux ise, redux’ın react binding’lerinin oluşturulmasını sağlıyor. Örneğin connect() gibi bir component’i redux’a bağlama fonksiyonu redux’ın içerisinde yüklü olarak gelmiyor. Ayrıca store’un app’e bağlanmasını sağlayan <Provider> bileşeni de react-redux ile birlikte geliyor. redux-logger ise redux için bir middleware olup, store’dan geçen her bir action’ın console’a log’lanmasını sağlar. redux-persist ise redux’taki bilgilerin kalıcı olarak localStorage’da tutulmasını sağlar. Bu sayede kullanıcı uygulamayı tekrar açtığında verilerin yeniden çekilmesine gerek kalmaz. redux-saga, side-effect içeren asenkron işlemlerin, generator function’lar ile alınıp işlenmesini sağlar. isomorphic-fetch ise, fetch’in bulunmadığı tarayıcı motorları için polyfill sağlar. normalizr kütüphanesi, verileri key-value şekline getirerek, tutarlı bir şekilde saklanmasını sağlar. lodash, bazı yardımcı fonksiyonları içerir.
Redux sistemini oturtmak için önce bazı dizinleri oluşturmamız lazım:
mkdir actions reducers sagas schemas services store
Öncelikle Dm ekranındaki mesajların network’ten çekilmesini sağlayalım. DmScreen.js’teki messages dizisini aşağıdaki gibi bir sitenin altına kaydettim:
https://api.myjson.com/bins/1herpk
actions dizininin içine gelelim ve redux action’larını tanımlayacağımız index.js dosyasını ve package.json’ı oluşturalım:
Kodu açıklayacak olursak, REQUEST, SUCCESS ve FAILURE sabitleri, action tipleri için birer ön ek teşkil ediyor. createRequestTypes fonksiyonu ile, bir işlem türü için 3 farklı action tipi oluşturuluyor. Örneğin createRequestTypes(‘MESSAGES’) şeklinde çağrıldığında aşağıdaki gibi action tipleri oluşturuluyor:
{
"REQUEST": "MESSAGES_REQUEST",
"SUCCESS": "MESSAGES_SUCCESS",
"FAILURE": "MESSAGES_FAILURE"
}
action fonksiyonu sayesinde, belirlenen parametreler ile bir action oluşturuluyor. messages sabitinin oluşturulması ile, REQUEST, SUCCESS ve FAILURE tipleri için action’lar oluşturuluyor.
Şimdi web isteklerini gönderecek olan api.js dosyasını oluşturalım:
api.js’i açıklayacak olursak öncelikle web isteklerinin gideceği bir API_ROOT tanımlıyoruz. Daha sonra callApi metodunda, gidilecek url’in parçasını ve normalizr kullanılarak, response’un nasıl dönüştüleceğini belirten bir schema tanımlıyoruz. fullUrl sabitini tanımlayarak gidilecek tam url’i oluşturuyoruz. fetch(fullUrl) metodu ile web servise istek yapıyoruz. Daha sonra gelen response’u json’a çevirip, schema’ya göre normalize ederek geri döndürüyoruz. export const fetchMessages ile mesajları getiren fonksiyonu uygulama içerisine sunuyoruz.
Şimdi schemas dizini içerisine mesajların normalize edileceği veri şemasını oluşturalım:
responseMessageSchema ile, messageSchema’dan oluşan bir Array’i messages: attribute’üne atamamızı sağlıyor. messageSchema’yı da bir üst satırda tanımlıyoruz.
Şimdi sagas dizini içerisine redux-saga elemanlarını oluşturalım:
sagas/index.js dosyasını sondan başa doğru anlatırsam daha açıklayıcı olacağımı düşünüyorum. redux-saga’nın asenkron işlemler için kullanıldığını belirtmiştim. Bu asenkron işlemler için gönderilen action’ları izleyen watcher’lar bulunuyor. Bu watcher’lar gelen action’a göre işi alt birimlere paslıyor. rootSaga fonksiyonunu oluşturarak, fork edilen tüm watcher’ları tek bir fonksiyonda topluyoruz. Bu fonksiyonu daha sonra store’a vereceğiz. function* olan yıldızlı fonksiyonlar ilginç gelmiş olabilir. Bu fonksiyonlar JavaScript’in generator fonksiyonlarıdır. return yerine yield keyword’ü ile veri döndüren bu fonksiyonlar, tekrar çağrıldıklarında daha önceden çağrıldıklarını hatırlayarak aynı veriyi geri döndürmezler. Ancak farklı bir veri yield edilmişse o döner. Örneğin:
function* generator(i) {
yield i;
yield i + 10;
}const gen = generator(10);console.log(gen.next().value) // 10
console.log(gen.next().value) // 20
console.log(gen.next().value) // undefined
Burada 3. bir yield olmadığı için undefined döndürüldü. redux-saga‘nın generator kullanmasının amacı daha kolay test yapılması içindir. çünkü yield terimleri sayesinde, redux-saga-tester içerisinde bulunan SagaTester ile api’den dönen değerler mock edilerek start/wait/stop gibi işlemler gerçekleştirilebiliyor.
Koda geri dönecek olursak rootSaga, watchLoadMessages fonksiyonunu kullanarak LOAD_MESSAGES action’ını take() metodu ile dinliyor. Daha sonra diğer bir generator olan loadMessages rutinini çağırıyor. loadMessages, select fonksiyonu ile daha önceden api’den gelip hafızaya alınan (memoized) mesaj listesini çekiyor. Eğer mesaj listesi varsa onu yield ediyor, yoksa fetchMessages fonksiyonunu çağırıyor. fetchMessages fonksiyonu, generic fetchEntity fonksiyonuna parametre geçerek api.fetchMessages fonksiyonunu bind ediyor. fetchEntity’de api’den dönen değere göre response’u store’a yield ediyor.
Şimdi reducers dizini içerisinde, saga’dan dönecek action’ları handle edecek olan reducer’ları oluşturalım:
Buradaki entities reducer’ı, store içerisinde tutulacak elemanların belirlenmesini sağlıyor. Burada şimdilik messages: {}
var fakat daha sonra stories: {}
ve posts: {}
gibi attribute’ler de eklenebilir. içerisinde ise lodash’in merge fonksiyonundan yararlanarak, mevcut state ile bu entity’leri birleştiriyoruz. errorMessage reducer’ında ise, api’den dönen hatayı state’e ekliyoruz. isLoading reducer’ında arayüzde görüntülenecek olan yükleniyor ibaresinin görüntülenmesini kontrol etmek için boolean değer döndürüyoruz. Eğer REQUEST ile biten bir action tipi geliyorsa loading’i göstermek için true, SUCCESS veya ERROR dönüyorsa loading’i gizlemek için false dönüyoruz. Daha sonra combineReducers metodu ile, tüm reducer’ları birleştirerek store’a verebileceğimiz bir adet rootReducer elde etmiş oluyoruz.
Şimdi store dizini altında redux store’unuzu oluşturalım:
Uygulamanın store’u görebilmesi için proje dizininde yer alan index.js’te bir Provider tanımlamak ve birkaç değişiklik yapmak gerekiyor. Aşağıdaki gibi değiştirelim:
Artık redux sistemini oluşturduk. Biraz uzun sürdü fakat bundan sonra eklenecek olan diğer api çağrıları için daha az efor sarf ederek zaman kazanacağız. Şimdi DmScreen.js içeriğini asenkron olarak mesajları çekecek hale getirelim:
Öncelikle burada DmScreen’e bir props parametresini ekledik. Bu parametre sayesinde store’a bağlandığımızda verileri uygun olarak ekrana basabileceğiz. useEffect fonksiyonu ile sayfa ilk açıldığında mesaj listesinin çekilmesini sağladık. Tanımladığımız onRefresh metodu ile, liste yenilendiğinde verilerin tekrar çekilmesini gerçekleştiriyoruz. FlatList’e eklediğimiz RefreshControl component’i sayesinde, listenin yukarıdan aşağıya doğru çekildiğinde gösterilecek olan pull-to-refresh özelliğini ekliyoruz. mapStateToProps ile, state’den dönen verileri props’a aktarıyoruz. mapDispatchToProps ile, store’a dispatch edilecek olan fonksiyonu props’a tanıtıyoruz. Daha sonra react-redux’ta bulunan connect fonksiyonu ile bileşeni store’a bağlıyoruz.
Uygulamayı tamamladığınızda aşağıdaki gibi bir Mesaj ekranı ile karşılaşacaksınız. Sadece refresh ettiğimde verilerin gelmesi için useEffect satırlarını yorum satırı haline getirdim:
Bu noktadan sonra artık redux-persist’i ekleyerek verileri kalıcı olarak localStorage’a yazıp okumak çok kolay. Bunun için store/index.js ve projedeki index.js dosyasını aşağıdaki gibi değiştirmek yetiyor:
Projenin son halini 7.1-adding-redux branch’ine giderek edinebilirsiniz.
git checkout 7.1-adding-redux
Sonuç olarak
Projede redux mimarisini kodladık ve ilgili diğer ayarlamaları yaptık. Bu bilgiler ışığında siz de kendi projenize redux’ı uyarlayabilirsiniz. Sonraki yazımda görüşmek üzere…