* * *

redux nedir

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ş.

redux state

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.

react component hierarchy

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'i
  • action: 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 yerine spread operator, concat() veya Object.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.

redux lifecycle

  • 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?

redux comment system

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.

* * *
Styled Components Nedir?Deno Nedir? Ne İşe Yarar?