Baştan Sona Redux Nedir? Nasıl Kullanılır?
React.js kullanmaya yeni başladıysanız sıklıkla redux kütüphanesini duymuşsunuzdur. Hatta öğrenmeye çalışıp pes etmişliğiniz bile olmuştur (ben de öyle yapmıştım 😁).
Yıllar önce redux öğrenmeye başladığım zamanlarda fazla kompleks ve gereksiz bir kütüphane olduğunu düşünmüştüm. Hatta öğrenme sürecini yarıda bırakıp mudaili olan mobx kütüphanesine şans vermiştim. Fakat react ve redux arasındaki karı koca ilişkisinden dolayı mecburen redux öğrenmeye tekrar başlamıştım 💩. Bu yazıdaki amacım sizlere en kolay yoldan bu kütüphanenin amacı ve nasıl kullandığı hakkında bilgi verebilmek.
Neler Öğreneceğiz?
- Redux nedir?
- State nedir?
- Neden redux kullanmalıyız?
- Redux temelleri nelerdir?
- Redux'ın çalışma prensibi nedir?
- Redux nasıl kurulur?
- Redux nasıl kullanılır?
- Birden fazla reducer nasıl kullanılır?
- Redux ile örnek bir uygulama geliştirme
Redux nedir?
Redux özetle state management yani uygulama genelinde olan bitenleri yönetebileceğimiz bir javascript kütüphanesidir. Uygulamamızdaki modalların yönetimden, kullanıcı giriş çıkışına kadar tüm dinamik işlemleri redux ile kontrol edebiliriz. Ayrıca redux çok geniş bir community'e sahip olduğu için sorunlarınızı çözmekte ve uygulamanızın ihtiyacı olan paketleri bulmakta zorlanmayacaksınız.
State Nedir?
Redux kütüphanesini kavrayabilmek için önce state kavramının ne olduğuna yakından bakmamız gerekir. State uygulama için gerekli ve uygulamanın lifecycle'ı içerisinde dinamik olarak değişebilen ve yönetilmesi gereken değişkenlerin bulunduğu bir javascript objesidir.
React.js kullanırken tanımladığımız state ile redux kullanırken tanımladığımız state arasındaki tek fark redux state'inin uygulama genelini kapsaması yani global bir state olmasıdır.
Aşağıda adobe'nin web sitesinden aldığım bir state örneğini bırakıyorum. Gördüğünüz gibi uygulama içindeki dinamik değerler objeler halinde oluşturulmuş.
Neden redux kullanmalıyız?
Yazılımcılar olarak bazen trendleri fazla önemseyip yaptığımız geliştirmeyi komplex hale getirebiliyoruz. Yani yazılımcı daima basit düşünmelidir ilkesine ters düşebiliyoruz. Bu yüzden önce öğrenmek veya kullanmak istediğimiz aracın ortaya çıkma sebeplerini ve çözdüğü problemleri iyice analiz etmemiz gerekiyor.
Ufak bir tavsiyeden sonra neden redux kullanmalıyız ve react'taki state mekanizmasından farkı ne gibi soruları cevaplamaya çalışalım. Öncelikle react.js'de state'ler her zaman component bazlı oluşturulur ve props yardımı ile diğer component'lara aktarılır.
💡 State'i props yardımıyla kullanmak isteyen component, state'nin tanımlandığı component'in child component'i olmak zorundadır.
Yukarıdaki görseli incelemeye çalışalım. Profile
component'inde kullanıcı bilgileri ve giriş yapılma durumu ile ilgili react state'i oluşturulmuş. Bu da demek oluyor ki kullanıcı ile ilgili tüm logic'i Profile
component'i barındırıyor. Kullanıcı bilgilerine NewComment
component'inde de ihtiyacımız olduğunu varsayalım. Bu durumda aşağıdaki yollardan birini izlememiz gerekecek.
- Kullanıcı bilgileri ile ilgili state'i
App
component'ine taşımak Profile
component'indeki state ve kontrollerin aynısınıNewComment
component'ine taşımak
🚫 Aşağıda açıklayacağım nedenlerden dolayı bu yolları tercih etmek mantıklı olmayacaktır.
Birinci yolu kullanmamalıyız çünkü ileride ne kadar nested component'a sahip olacağımızı bilemeyiz. 10 seviye alttaki bir component'a kullanıcı bilgilerini props ile taşımak çok doğru olmayacaktır.
İkinci yolu da kullanmamalıyız çünkü kullanıcı bilgilerine ihtiyaç duyan component arttıkça code duplicate (kod tekrarı) problemleri ortaya çıkacaktır.
İşte tam bu noktada redux devreye giriyor. Uygulama genelinde bir state oluşturup dilediğimiz component'in bu state'e kolayca ulaşabilmesi için redux kullanıyoruz 😍.
Redux temelleri nelerdir?
Redux aşağıdaki dört temel yapıdan oluşur. Hepsinden teker teker ve detaylı bir şekilde bahsetmeye çalışacağım.
- Actions
- Dispatch
- Reducers
- Store
Actions
Redux'ın uygulama genelinde global bir state oluşturduğunu söylemiştik. Actions ise bu global state'e gönderilecek verinin gövdesidir.
💡 Bir action her zaman javascript objesi olmak zorundadır.
Bir action genellikle type
ve payload
olmak üzere iki adet property'den oluşur. type
action'un belirteçi hangi işlemin yapıldığını belirtir. payload
ise gönderilecek veriyi içeren property'dir. payload
yerine gönderdiğiniz verinin adını da (movie, comment vs.) yazabilirsiniz fakat genel bir kural olarak payload
property'sini kullanmaya özen göstermelisiniz.
// "ADD_COMMENT" Action
{
type: "ADD_COMMENT",
payload: {
userId: 41251,
commentBody: "Gayet güzel bir filmdi."
},
}
// veya
{
type: "ADD_COMMENT",
comment: {
userId: 41251,
commentBody: "Gayet güzel bir filmdi."
}
},
Dispatch
Dispatch bir redux method'udur ve redux state'ini güncellemek için kullanılır. Dispatch method'u parametre olarak yukarıda bahsettiğimiz action tipinde bir obje alır.
dispatch({
type: "GET_POST",
payload: {
postId: 23
}
})
Reducers
Reducers global state'in güncellendiği, kontrol edildiği ve güncellenmiş state'i return eden klasik bir javascript function'udur. Aşağıda açıklacağım iki parametre alır:
state
: uygulamanın geçerli state'iaction
: global state'i güncellemek için gönderilen action
Reducer oluşturmak için aşağıdaki kurallara uymak zorundasınız.
- Reducer'ın
state
parametresini her zaman başlangıç state'ine eşitleyin. - Oluşturduğunuz reducer her zaman benzersiz bir obje dönmelidir.
.push()
,.splice()
gibi methodlar yerinespread operator
,concat()
veyaObject.assign()
gibi yöntemler kullanın.
Öyleyse ilk reducer'ımızı oluşturalım.
const initialState = {
comments: [{
commentId: 1
commentBody: "İlk film daha aksiyon doluydu."
}]
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case "ADD_COMMENT":
return {
...state,
comments: [...state.comments, action.payload]
}
default:
return state
}
}
Bahsettiğim kurallara uygun reducer örneğimizi yukarıda oluşturmak olduk. Dilerseniz switch/case
yerine if/else
yapısını da kullanabilirsiniz.
Store
Yazının bu kısmına kadar bahsettiğimiz global state'in redux karşılığı store'dur. Tüm reducer'ların birleşimiyle oluşturulur. Redux store oluşturmak için redux içinde bulunan createStore()
method'unu kullanırız.
import { createStore } from 'redux'
import commentReducer from './commentReducer'
const store = createStore(commentReducer)
Redux ile oluşturduğumuz store objesinde aşağıdaki üç adet method bulunur.
store.getState()
: güncel state objesini verir.store.dispatch()
: reducer'a state'i güncellemesi için action gönderir.store.subscribe()
: store'da oluşan değişiklikleri dinler.
Redux'ın çalışma prensibi nedir?
Redux'ın nasıl çalıştığını anlamak için güzel ve sade bir grafik hazırladım. Bu grafik üzerinden sırayla redux'ın nasıl bir çalışma prensibi olduğunu anlatmaya çalışacağım.
- Kullanıcı view'da herhangi bir action dispatch eder.
- Action dispatch method'u ile reducer'a iletilir.
- İletilen action reducer'da işlendikten sonra global store güncellenir.
- Store'daki güncellemeler view'a yansıtılır.
Redux nasıl kurulur?
Şu ana kadar redux hakkında fazlasıyla teorik fakat faydalı bilgiye sahip olduk. Yazının bu kısmından itibaren sadece redux kullanarak ufak bir yorum sistemi yapmaya çalışacağız. Localinizde veya Codesandbox gibi online araçları kullanarak bir proje oluşturabilirsiniz. Projeyi oluşturduğunuzu varsayıp redux kurulumuna geçelim.
Redux' ister npm veya yarn ile isterseniz de cdn aracılığı ile projenize ekleyebilirsiniz. Gerçek bir proje deneyimi için npm veya yarn'ı seçmek mantıklı olacaktır.
npm install redux
yarn add redux
Redux nasıl kullanılır?
Bu kısımda yukarıdaki yorum sistemini entegre etmeye başlayacağız. Öncelikle gerekli dosyaları oluşturmakla başlayalım. index.js
, store.js
, index.css
ve index.html
dosyalarını oluşturalım. Konumuz styling olmadığı için index.css
dosyasında yapacağımız geliştirmeleri yazının sonunda paylaşacağım linkten ulaşabilirsiniz.
Öncelikle html markup oluşturalım.
index.html
dosyamıza yorum sayımızı göstermek için #comment-count
, form elemanı için #comment-form
ve yorumlarımızın listeleneceği alan için #comments-container
id'li elemanlar ekleyelim.
<!DOCTYPE html>
<html>
<head>
<title>Pure Redux Basic Comment System</title>
<meta charset="UTF-8" />
</head>
<body>
<h1>Yorumlar (<span id="comment-count"></span>)</h1>
<form id="comment-form">
<textarea id="comment-input" rows="5"></textarea>
<button>Yorum Ekle</button>
</form>
<div id="comments-container"></div>
<script src="src/index.js"></script>
</body>
</html>
Ardından şimdiye kadar öğrendiğimiz redux bilgileri ışığında store.js dosyamızda geliştirmeler yapmaya başlayalım.
Önce store oluşturmak için kullandığımız createStore
method'u ve initialState
objemizi ekleyelim.
import { createStore } from "redux";
const initialState = {
comments: [
{
id: generateId(),
body: "Gayet güzel bir film."
},
{
id: generateId(),
body: "Beğenmedim."
},
{
id: generateId(),
body: "Oyuncular çok başarılıydı."
}
]
};
Yukarıda gördüğünüz gibi yorum id'si üretmek için generateId()
method'unu oluşturalım. Bu method 1'den 95'e kadar random sayılar üretmelidir. Neden 95 dediğinizi duyar gibiyim 🤔? Yorum sisteminde kullanacağımız gereken fotoğraflar için randomuser.me sayfasını kullanacağız ve 95 kadın arasından rastgele fotoğraf göstereceğiz. Öyleyse hemen generateId()
method'unu oluşturalım ve initialState
objemizin üstüne ekleyelim.
export const generateId = () => Math.floor(Math.random() * 95) + 1;
Method'umuzu farklı sayfalarda kullanmak için export ettik.
Şimdi de commetReducer
adında bir reducer oluşturalım. Bu reducer'a "ADD_COMMENT"
adında bir action ile gelindiğinde payload'da bulunan yorumu state'in başına ekleyecektir.
const commentsReducer = (state = initialState, action) => {
switch (action.type) {
case "ADD_COMMENT":
return {
...state,
comments: [action.payload, ...state.comments]
};
default:
return state;
}
};
İlk başta eklediğimiz createStore
method'u ile store'umuzu oluşturalım ve kullanılmak üzere export edelim.
export default createStore(commentsReducer);
index.js
dosyamızı açalım ve oluşturduğumuz store object'ini ve generateId
method'unu import edelim.
import store, { generateId } from "./store";
Html dosyamızda id'lerle beraber oluşturduğumuz element'leri tanımlayalım.
const commentForm = document.getElementById("comment-form");
const commentInput = document.getElementById("comment-input");
const commentCount = document.getElementById("comment-count");
const commentsContainer = document.getElementById("comments-container");
Yorum ekle form'u için submit
method'u oluşturalım.
commentForm.addEventListener("submit", e => {
e.preventDefault();
store.dispatch({
type: "ADD_COMMENT",
payload: {
id: generateId(),
body: commentInput.value
}
});
commentInput.value = "";
});
Böylece yorum form'u submit edildiğinde store'u güncellemiş olduk. Güncellenen store'u view'da göstermek için yorumlarımızı ve yorum sayısını render edebileceğimiz methodları oluşturalım.
// Render Comments
const renderComments = () => {
const { comments } = store.getState();
const commentList = comments
.map(
({ body, id }) =>
`<div>
<img src="https://randomuser.me/api/portraits/women/${id}.jpg" />
<span>${body}</span>
</div>`
)
.join("");
commentsContainer.innerHTML = commentList;
};
// Render Comment Count
const renderCommentCount = () => {
const { comments } = store.getState();
commentCount.innerHTML = comments.length;
};
Tüm bu geliştirmelerden sonra hala sayfada istediğimiz görüntüye ulaşamadık. Çünkü renderComments()
ve renderCommentCount
function'larımızı initialize etmemiz yani sayfa ilk açıldığında çalıştırmamız gerekir. Bu yüzden aşağıdaki satırları ekleyelim.
renderComments();
renderCommentCount();
Yorum ekle butonuna bastığınızda yeni yorumu göremediyseniz korkmayın çünkü son bir ekleme daha yapmamız gerekiyor. store objesinin üç adet method'u olduğundan bahsetmiştik. store.subscribe()
method'unu kullanarak global state güncellendiğinde renderComments()
ve renderCommentCount
function'larının yeniden çalışmasını sağlamalıyız. Hemen kodumuzu güncelleyelim.
store.subscribe(() => {
renderComments();
renderCommentCount();
});
Uygulamamızın son halini aşağıya bırakıyorum.
Birden fazla reducer nasıl kullanılır?
Şu ana kadar sadece tek bir reducer senaryosu üzerinden ilerledik. Fakat gerçek hayatta onlarca reducer'a sahip olabiliriz. Bu noktada redux method'larından biri olan combineReducers()
kullanmamız gerekiyor. Hemen bir örnekle göstermeye çalışayım. Comment ve user olmak üzere iki adet reducer'ımız olduğunu düşünelim ve bu reducer'ları birleştirelim.
import { combineReducers, createStore } from "redux";
const commentsReducer = (state, action) => state
const userReducer = (state, action) => state
const combinedReducers = combineReducers({
comments: commentsReducer,
user: userReducer
})
export default createStore(combinedReducers)
User state'ine ulaşmak için store.getState().user
şeklinde bir kullanım yapmamız gerekiyor.
Kapanış
Uzun soluklu bir yazının ardından redux hakkında birçok şeyi deneyimlemiş ve fikir sahibi olduğunuzu düşünüyorum. Bu yazıda sadece redux kütüphanesi üzerinde durdum ve bu kütüphaneyi baştan sona kavramınızı istedim. Sonraki yazılarımda react ve redux entegrasyonu, redux middleware kavramı ve redux thunk nedir gibi konulara deyineceğim.
Yorumlar
Soru, cevap ve destekleriniz için aşağıdan yorum bırakmayı unutmayın.