Redux Toolkit nedir ? Neden tercih etmeliyim.

Kubilay
17 min readOct 16, 2023

--

Redux Nedir?

Tek bir cümle ile açıklamak gerekirse , geliştirdiğimiz projelerde global olarak stateleri yönetmemizi ve bu statelere müdahale etmemizi sağlayan bir teknoloji.
Global olarak yönetmek derken neyden kast ettiğimi bir örnek üzerinden açıklayayım öncelikle.Bildiğiniz gibi son dönemlerde uygulamalarda tema özellikleri söz konusu olmaya başladı örnek olarak twitter’ı ele alalım.

React ile geliştirilen Twitter’ın genel olarak layout kısmını inceleyelim ve konumuza uygun olacak şekilde bir layout haritası oluşturup uygulamamızı componentlere bölelim. Dark yada white mode için classname içinde bir kontrol gerçekleştirdiğimizi ve ona göre classların aktif pasif olacak şekilde uygulamanın UI kısmının render edildiğini düşünelim.

Gördüğünüz gibi sağ tarafta bir navigation menümüz orta alanda yukarıdan aşağıya doğru ; kullanıcı bilgileri içeren bir component onun altında bir tab bar mevcut. Sol tarafta ise tekrar yukarıdan aşağıya doğru takip önerileri ve gündem kısmı bulunmakta.

Şimdi burada navigation kısmının bir component, kullanıcı bilgilerinin olduğu alanı başka bir comp. tab bar kısmını başka bir component olarak düşünelim. Uygulamamızda temayı değiştiren butona tıkladığımız zaman parçalara ayırdığımız ve projemizde ayrı ayrı javascript yada typescript olarak geliştirilen bu componetlerin sahip olduğu css kodlarının o anki koşula göre değişmesi gerekiyor değil mi ?

Burada navigation menüsünde bulunan Tweet butonunun hemen altında başka bir buton olduğunu düşünelim, bu butona basınca uygulamamızda bulunan bir state üzerinde bir değişim gerçekleşecek ve biz bu sayede menümüzü dark yada white olarak değiştirecek şekilde bir geliştirme yaptığımızı düşünelim , butona bastık ve temamız dark ise white oldu (yada tam tersi).Şimdi bu değişimi yaptıktan hemen sonra bizim orta alanda bulunan kullanıcı sayfamızın temasına müdahale edebilmemiz için nasıl bir yöntem izlememiz gerekiyor ?

Hemen aklınıza props olarak göndermek geliyor değil mi ? Peki o zaman burada her bir component içinde props olarak bu tema state’i aktarılacak ise parent olan bir componente biz bu state değerini nasıl yollayacağız? State’i parent içinde tanımlarız o zaman dediğinizi duyar gibiyim peki parent içinde state tanımını gerçekleştirdik ve bunu child componentlere props olarak aktarırken ne kadar fazla maliyet ve ne kadar çok fazla karmaşa ortaya çıkacak düşüncesi bile insanın kafasını karıştıracak cinsten değil mi ? İşte bu ve buna benzer bir durumda uygulamızda global olarak bu tema değerini tutacağımız bir state olması gerekiyor ve bu state’e her bir component içinde erişerek gerekli işlemleri yapmamız gerekiyor.

Bu verdiğim örnek aslında pek tercih edilmeyen ve kullanılmayan bir yöntem günümüzde bahsettiğim tema güncelleme işlemleri için bir çok farklı yöntem bulunmakta fakat örnek olarak konunun en kolay izah edileceği örneğinde bu olduğunu düşünüyorum.

Şimdi gelelim asıl konumuza global olarak state yönetimi için neden redux-toolkit kullanmalıyım.

Redux Toolkit, Redux tabanlı uygulamalar geliştirmeyi daha kolay ve verimli hale getiren bir kütüphanedir. Redux, JavaScript uygulamalarında durum yönetimi için yaygın olarak kullanılan bir kütüphanedir. Ancak Redux, kullanımı bazen karmaşıklaşabilen çok fazla tekrarlayan kod gerektirebilir. Redux Toolkit, bu sorunları çözmek ve Redux’i daha kullanıcı dostu hale getirmek için tasarlanmıştır.

Redux Toolkit’in bazı temel özellikleri şunlardır:

  • Azaltılmış Boilerplate Kod: Redux Toolkit, tipik Redux uygulamalarında yazmanız gereken tekrarlayan kod miktarını azaltır. “createSlice” ve “createAsyncThunk” gibi yardımcı işlevlerle, reducer’larınızı, eylemlerinizi ve çok sayıda işlemi daha az kodla tanımlamanıza olanak tanır.
  • İçe Aktarmaların Kolaylığı: Redux Toolkit, başka dosyalardan Redux özelliklerini içe aktarmayı ve kullanmayı kolaylaştırır. Bu sayede kodunuz daha organize ve okunur hale gelir.
  • Özelleştirilebilirlik: Redux Toolkit, her bir özelliği özelleştirmenize ve karmaşık kullanım senaryolarını ele almanıza olanak tanır. Bu, büyük ölçekli uygulamalarda esneklik sağlar.
  • Asenkron İşlemleri Kolaylaştırma: “createAsyncThunk” ile Redux Toolkit, asenkron işlemleri yönetmeyi basitleştirir. API çağrıları ve diğer asenkron işlemler için otomatik olarak eylemler oluşturur ve hata işleme yetenekleri sunar.

Redux Toolkit’i tercih etmenin bazı avantajları şunlar olabilir:

  • Daha az kod yazma: Redux Toolkit, Redux’in sunduğu özellikleri daha az kodla kullanmanızı sağlar, böylece geliştirme süreci daha hızlı hale gelir.
  • Daha kolay bakım: Azaltılmış boilerplate kod, uygulamanızın bakımını daha kolay ve sürdürülebilir hale getirir.
  • Asenkron işlemleri yönetme kolaylığı: Redux Toolkit, asenkron işlemleri kolayca entegre etmenizi ve yönetmenizi sağlar.
  • Daha iyi organizasyon: Redux Toolkit, kodunuzu daha düzenli bir şekilde organize etmenize yardımcı olur.

Ancak Redux Toolkit’i tercih etmek, projenizin gereksinimlerine ve ekibinizin deneyimine bağlıdır. Küçük projelerde veya Redux’i daha derinlemesine anlamak istediğiniz durumlarda, doğrudan temel Redux kullanabilirsiniz. Redux Toolkit, özellikle büyük ölçekli projelerde ve daha hızlı geliştirme süreçleri gerektiren durumlarda büyük bir avantaj sağlayabilir.

NextJS ve Redux kullanarak bir proje geliştirelim.

İlk olarak bir NextJS uygulaması oluşturalım.

npx create-next-app redux-toolkit-medium

Daha sonra projemize Redux Toolkit’i kuralım.

npm install @reduxjs/toolkit react-redux

Şimdi kurulumlarımızı hallettikten hemen sonra ilk olarak yapmamız gereken bir adet store oluşturmak.

Store nedir ?

Redux store, Redux’ın uygulama durumunun yönetildiği merkezi bir depodur. Redux store, uygulama verilerini ve durumunu tek bir yerde saklayarak, bu verilere erişim ve güncelleme işlemlerini düzenler.

Şimdi uygulamızın içine bir adet Redux adında klasör ekleyelim.
Daha sonra bu klasör içine store adında bir js dosyası oluşturalım bu store dosyasının içine ekte bulunan kodları yazalım

import { configureStore } from '@reduxjs/toolkit'

export const store = configureStore({
reducer: {},
})

Redux Toolkit kullanırken configureStore fonksiyonunda tanımlanan reducer alanı, uygulamanızın durumunu yöneten kesimlerin (slices) veya özel reducer'ların birleştirildiği yerdir. Reducer'lar, eylemleri işleyen ve uygulama durumunu güncelleyen işlevlerdir.
Şimdi burada anlattıklarım biraz teknik oldu farkındayım slice hakkında hiçbir bilgi vermeden slice lar burada tanımlanır gibi bir cümle kurdum .
Şimdi slice nedir ona bakalım .

Slice nedir ?

Redux Toolkit’de “slice,” bir uygulamanın durumunun yönetildiği ve bu durumun bir parçasını temsil eden bir kavramdır. Sliceler, reducer fonksiyonları ve bu slicelerin başlangıç durumları (initial state) dahil olmak üzere birçok özelliği içerir. Slice’lar, Redux uygulamalarını daha düzenli ve modüler hale getirmek için tasarlanmıştır.

Bir Redux Toolkit slice’inin temel özellikleri şunlardır:

name: Slice'ın adıdır. Bu, store içindeki slice'ı tanımlamak için kullanılır ve genellikle eylemler ve reducer'lar arasında iletişim kurmak için kullanılır.

reducer: Slice'ın ana reducer fonksiyonunu tanımlar. Bu reducer, slice'ın başlangıç durumunu ve eylemleri işler.

initialState: Slice'ın başlangıç durumunu temsil eder. Bu, slice'ın reducer'ı tarafından kullanılır ve uygulama başladığında slice'ın ilk durumunu tanımlar.

Şimdi Redux klasörünün hemen altına features adında başka bir klasör oluşturalım.Şimdi daha sonra counter adında başka bir klasör oluşturup bunun içine counterSlice adında bir js dosyası oluşturalım. Bu dosyanın içinde slice tanımını gerçekleştirelim.

import { createSlice } from '@reduxjs/toolkit'

const initialState = {
value: 0,
}

export const counterSlice = createSlice({

/*
Name =>
Bu, store içindeki slice'ı tanımlamak için kullanılır ve
genellikle eylemler ve reducer'lar arasında iletişim kurmak için kullanılır.

Reducer =>
Slice'ın ana reducer fonksiyonunu tanımlar.
Bu reducer, slice'ın başlangıç durumunu ve eylemleri işler.

initialState =>
Slice'ın başlangıç durumunu temsil eder.
Bu, slice'ın reducer'ı tarafından kullanılır ve uygulama başladığında slice'ın ilk durumunu tanımlar.
*/

name: 'counter',
initialState,
reducers: {
increment: (state) => {
state.value += 1
},
decrement: (state) => {
state.value -= 1
},
incrementByAmount: (state, action) => {
state.value += action.payload
},
},
})

// Action creators are generated for each case reducer function
export const { increment, decrement, incrementByAmount } = counterSlice.actions

export default counterSlice.reducer

Şimdi tüm uygulamızı burada kapsayacak şekilde redux’ı kullanarak projemize entegre etmemiz lazım evet evet Provider kullanacağız dediğinizi duydum :)

Daha önce oluşturduğumuz Redux klasörünün hemen altına bir adet ReduxProvider adıda yeni bir javascript dosyası oluşturalım ve provider’ı burada oluşturalım.

"use client";
import React from "react";
import { store } from "./store"; //Kendi oluşturduğumuz store.
import { Provider } from "react-redux";

export default function ReduxProvider({ children }) {
return (
<Provider store={store}>
{children}
</Provider>
);}

Şimdi app dosyasının hemen altında bulunan layouth.js içine oluşturduğumuz bu provider’ı kullanarak sarmalama işlemini gerçekleştirelim.

import ReduxProvider from "./Redux/ReduxProvider";
import "./globals.css";

//....

export default function RootLayout({ children }) {
return (
<html lang="en">
<body className={inter.className}>
<ReduxProvider>{children}</ReduxProvider>
</body>
</html>
);
}

Şimdi kurulumumuz bitti sayılır yapmamız gereken çok küçük bir kaç adet kaldı.
İlk olarak oluştuşmuş olduğumuz bu slice yapısını oluşturduğumuz Store içinde tanımlamamız gerekiyor.
Bunun için ilk olarak Store dosyamıza giriş yapalım ve reducer objesinin içinde oluşturduğumuz slice dosyasını ekleyelim.

import { configureStore } from "@reduxjs/toolkit";
import counterReducer from './features/counter/counterSlice'

export const store = configureStore({
reducer: {
Mycounter: counterReducer, //Oluşturduğumuz slice. [key,Reducer]
},
});

Gördüğünüz gibi burada Mycounter adını kullanarak counterReducer’a erişmek istediğimizi store içine bildirdik.

Ufak bir dipnot eklemek istiyorum . Dosyalarımızı alt kısımda bulunan görsele göre yaptık.

Şimdi oluşturduğumuz counter slice’ını kullanarak basit bir counter uygulası geliştirelim.

//Page.js dosyamız.
import React from "react";
import Counter from "@/Components/Counter";

export default function Home() {
return (
<div className="flex flex-1 justify-center items-center h-screen">
<Counter />
</div>
);
}
//Counter componentimiz
"use client";
import React from "react";
import { useSelector } from "react-redux";
import { decrement, increment } from "../Redux/features/counter/counterSlice";
import Button from "./Button";
export default function Counter() {
const count = useSelector((state) => state.Mycounter.value);
return (
<div className="custom-number-input h-22 w-64">
<label className="w-full text-gray-700 text-sm font-semibold">
Counter Input
</label>
<div className="flex flex-row h-10 w-full rounded-lg relative bg-transparent mt-1">
<Button Text={"-"} func={decrement} />
<span className=" focus:outline-none text-center w-full bg-gray-300 font-semibold text-md hover:text-black focus:text-black md:text-basecursor-default flex items-center text-gray-700 outline-none">
{count}
</span>
<Button Text={"+"} func={increment} />
</div>
</div>
);
}
import { useSelector, useDispatch } from "react-redux";
export default function Button({ Text, func }) {
const dispatch = useDispatch();
return (
<button
onClick={() => dispatch(func())}
className=" bg-gray-300 text-gray-600 hover:text-gray-700 hover:bg-gray-400 h-full w-20 cursor-pointer outline-none"
>
<span className="m-auto text-2xl font-thin">{Text}</span>
</button>
);
}

Şimdi uygulamızı Redux DevTools üzerinden inceleyelim.

Gördüğünüz gibi her bir tıklamada state değişiyor . Child Parent ilişkisine bağlı olarak herhangi bir şekilde props gönderme işlemi gerçekleştirmedik , tamamiyle Redux üzerinden verileri aldık yada verilere increment,decrement işlemi gerçekleştirdik.

Şimdi Biraz daha advance bir çalışma gerçekleştirelim.
Öncelikle kaynak kodlarını paylaşayım ki zamandan tasarruf sağlayalım.

App klasörünün hemen altına Blog adında bir klasör oluşturalım.

Daha sonra Components adı altında bir klasör daha oluşturup içine PostList PostForm ve ReactionButtons adında 3 farklı dosya oluşturalım.

PostForm dosyamız:

PostList Dosyamız.

Şimdi Slice Dosyamızı oluşturup bu slice dosyamızı store içine ekleyelim.

//Store dosyamız.

import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./features/counter/counterSlice";
import TodoReducer from "./features/Todo/TodoSlice";
import BlogReducer from "./features/Blog/BlogSlice";

export const store = configureStore({
reducer: {
Mycounter: counterReducer,
MyTodo: TodoReducer,
Blogs: BlogReducer,
},
});

Kodumuzda ufak tefek hatalarımız söz konusu olabilir bunu bilerek yaptım şimdi beraber bunları düzenleyeceğiz ve daha advance bir şekilde projemizi oluşturacağız.Tarif ettiğim şekilde düzenlemeleri yaparsanız uygulamamız bu şekilde gözükecek.

Şimdi öncelikle düzenlemelerimize başlayalım ,
Şimdi uygulamamıza bir kaç adet yeni özellik tanımlayalım ve bu özellikler üzerinden slice üzerinde güncellemelerimizi gerçekleştirelim.
→ Öncelikle her bir post için bir adet tarih bilgisi tutalım.
→ Öncelikle her bir post için bir adet User bilgisi tutalım.
→ Öncelikle her bir post için bir adet Reaksiyon bilgisi tutalım.

Şimdi bu özellikleri öncelikle slice içinde initialState içinde ekleyelim.

Daha sonra uygulamamız içinde düzenlemelerimizi yapalım.
Öncelikle Component altında bulunan Blog dosyamızın içine yeni bir dosya ekleyelim adının ReactionButtons olarak ayarlayalım daha sonra kodlarımızı ekteki gibi düzenleyelim.

Daha sonra ilk yapmamız gereken işlem PostList içinde bu değeri component’in içine props olarak post objesini yollamak.

Şimdi User kısmına geçiş yapalım User için şöyle bir yöntem izleyelim, hem konuyu kavrama hemde konu üzerinde daha fazla pratik yapmamızı sağlayacak olan bu yöntemde başka bir slice’da yararlanarak bu işlemleri gerçekleştirelim.

Hemen Slice’ımızı oluşturalım.(Tamamen aynı yöntem olacağı için tekrar görsel üzerinden göstermek istemedim, Redux klasörü içinde features içine User Klasörü oluşturup içine bir adet UserSlice adında js dosyası oluşturduk.)

Şimdi ilk olarak ReactionButtons için yaptığımız işlemlerin aynılarını PostAuthor isimli bir dosya oluşturup yeniden uygulayalım.

Store dosyamızın içinde hemen UserSlice’ını tanımlayalım.

Şimdi uygulamamıza bir göz atalım.Gördüğünüz gibi değerlerimiz artık dolu gelmeye başladı. Şimdi form üzerinden bir post ekleme işlemi gerçekleştirelim.

Gördüğünüz gibi hata mesajı aldık bunun sebebi ,
Redux burada state güncellerken Emoji ve User info değerlerinin ne olduğunu bilmiyor. Biz burada redux’da yeni bir state eklerken bu değeleri henüz aylarlamadık şimdi bu değerleri ayarlamaya başlayalım.

Biz Input değeri olarak sadece Title ve Content değerlerini ele aldık.(PostForm.js dosyamızın içinde bulunan Save Methodumuz.)

Şimdi tahmin edeceğiniz üzere burada eksik alanları doldurmamız gerekiyor değil mi? Şimdi bazı alanlar manuel olacak şekilde alanları dolduralım.

Şimdi uygulamamızı tekrar kontrol edelim.

Evet başarılı bir şekilde ekleme yapabiliyoruz.
Şimdi gelelim başka bir konuya öncelikle kaç dakika önce paylaşılmış bunu belirten bir component ekleyelim projemize.

Components dosyamızın altında bulunan Blog dosyasının içinde TimeAgo adında bir javascript dosyası oluşturalım.

Daha sonra bunu PostList içine yerleştirelim.

Şimdi uygulamamıza tekrar bi göz atalım.

Şimdi neden 10 dakika neden 5 dakika önce falan diyebiliriz,buna takılmayalım şimdilik burada bu amacımız sadece biraz dava advance özellik kazandırmak uygulamamıza.

Şimdi kullanıcı bilgilerini içeren Author Select input alanına geçiş yapalım. Burada öncelikle Author için Postform içinde bulunan Select kısmında düzenleme yapalım.

Select kısmının option kısmına Daha önce oluşturduğumuz UserSlice içinde bulunan verileri render edecek şekilde kodumuzu düzenleyelim.

Bunun için tabi UseSelector kullanarak User bilgilerini Redux’dan almamız gerekiyor.

//PostForm içinde User State değerlerini almak için gerekli.
const users = useSelector(selectAllUsers);

Şimdi User bilgilerinide redux üzerinde güncelleyecek şekilde kodumuzu düzenleyebildik.

Son bir adım kaldı Reaksiyon değerlerini güncelleme işlemi burada reducer kullanarak bu işlemi gerçekleştirelim. Zaten bu konu hakkında artık fikir sahibiyiz bundan dolayı doğrudan slice içinde reducer içinde kodumuz nasıl gözükecek onu eklemek daha mantıklı olacak.

reactionAdded adında bir reducer ekledik bu fonksiyon sayesinde gerekli olan işlemleri yapacağız.

Şimdi son olarak ReactionButton adındaki dosyamızı düzenleyelim.

Artık Uygulamamızı tamamladık şimdi optimizasyonlara başlamadan önce save için çalışan fonksiyonumuzda verileri dinamik olarak veriler gelecek şekilde düzenleyelim ve uygulamamıza bir göz gezdirelim.

Gördüğünüz kullanıcı bilgisi seçilen kullanıcıya göre ve tarih’te eklenme anına göre state içine ekleniyor ek olarak state içinde bulunan Reaction butonlarıda çalışmakta yani kısaca görüldüğü gibi her şey takırında çalışıyor :)

Optimizasyonlara başlayalım .

Şimdi PostForm dosyamızın son halini inceleyelim.

Yukarıda bulunan görselde görüldüğü gibi kodumuz aslında hiç temiz değil state sayımız artıkça AddPost reducer’i içine gönderdiğimiz sadece 3 değer var ve bizim default olarak 0 yada herhangi bir değer olarak ayarladığımız bir çok alan bulunmakta örnek olarak id değeri burada Guid kullanabiliriz fakat bu ekstra bir kütüphane gerektirecek.

Peki burada sadece bizim Redux’a gönderilen verilere göre state oluşturabilecek ve kalan verilerin ise defult değerlere sahip olacak bir şekilde bu reducer fonksiyonunu düzenlemenin bir yöntemi var mıdır sizce?

Evet var :)

Bunun için Redux Toolkit’de CreateSlice içinde bulunan Prepare methodunu kullanacağız

Prepare nedir ?
@reduxjs/toolkit içinde bulunan createSlice fonksiyonunun hemen altında bulunan prepare yöntemi, slice içinde tanımlanan reducer fonksiyonlarına uygun bir şekilde eylem nesneleri oluşturmayı kolaylaştırmak için kullanılır. Bu yöntem, genellikle slice içindeki işlevlerin temiz ve sade bir şekilde tanımlanmasına yardımcı olur.

prepare yöntemi, createSlice ile tanımlanan reducer'larınızın içinde kullanılan her bir eylemin bir eylem yaratma fonksiyonunu döndürmek için kullanılır. Bu, eylem yaratma işlemine ekstra bir soyutlama ekler ve eylem yaratma mantığını reducer işlevinden ayırır.

ilk olarak BlogSlice içine girip AddPost methodumuzu düzenleyeceğiz.

Artık PostForm içinde düzenleme yapabiliriz.

Gördüğünüz gibi kodumuz ne kadar clean bir hale geldi değil mi :)

Kısa bir süre sonra bu yazıma ek olarak Thunk yapısı Immer yapısı ve Optimizasyonlar hakkında eklemeler yapacağım.

Şimdilik hoşçakalın :)

Redux-Toolkit Thunk Yapısı

Bir Alışveriş sepeti uygulaması düşünelim , bu uygulama backend bağlantısına sahip bir sepet uygulaması olsun. Biz kendi uygulamamızda kendimiz için ihtiyacımız olan ürünleri yazmış olalım buna ek olarak eşimizde kendi bilgisayarında bu uygulama üzerinden eklemeler yapmış olsun. Şimdi bizim tutarlı bir projemiz olması için uygulamaya giriş yaptığımızda hem bizim ihtiyaçlarımız hemde eşimizin ihtiyaçlarının ekranda listelenmesi gerekiyor değil mi ? Yoksa biz alışveriş yaptıktan sonra eksik ürünler olduğunu fark edip tekrar alışverişe gitme durumunda kalabiliriz :))

İşte bu gibi durumlarda bizim uygulamamızda BackEnd üzerinden bir api ile eşimizinde eklediği ürünlere bir şekilde ulaşmamız lazım . Hmm bunun için akıllara birden fazla yöntem geliyor değil mi ama asıl konumuz Redux olduğu için Redux üzerinden ilerleyeceğiz şimdi bizim burada ihtiyacımız olan şey ne bir api call işlemi değil mi ? Hmm peki bu işlem anında gerçekleşecek bir işlem mi ? Tabikide hayır belki 1 saniye sürecek belki 10 saniye sürecek yani asenkron bir işlem olacak . Şimdi bir action gerçekleşecek gerçekleşen bu action bir şekilde api üzerinden reducer kullanarak state üzerinde bir güncelleme yapmak zorunda değil mi ?
Peki burada ne kullanacağız? Bize bir ara katman gerekiyor bu ara katman sayesinde gerekli işlem yapılıp state üzerinde değişikliklerin uygulanması gerekiyor değil mi .

Ara katman diyince aklımıza ilk gelen bir middleware değil mi ? İşte Redux burada Thunk adında bir middleware’ı bize sunuyor bu middleware sayesinde asenkron olarak işlemler yapabiliyoruz.

Şimdi işlemlere başlamadan önce teknik bir açıklama paylaşmak istiyorum sizinle;

createAsyncThunk, Redux Toolkit'in bir parçasıdır ve Redux için async işlemleri yönetmek için geliştirilmiş bir yardımcı işlevdir. Redux Toolkit, Redux'u daha verimli ve kolay kullanılabilir hale getirmeyi amaçlayan bir kütüphane ve bu kütüphane içinde yer alır.

createAsyncThunk, Redux uygulamalarında yaygın olarak kullanılan aynı döngülerin (örneğin, veri alma, gönderme veya güncelleme gibi) basit ve tekrar kullanılabilir bir yolunu sunar. Bu döngüler, genellikle üç aşamadan oluşur: "Başlat" (request), "Başarılı" (success) ve "Başarısız" (failure). createAsyncThunk, bu iş akışını kolaylaştırmak ve karmaşıklığı azaltmak için kullanılır.

Thunk yapısı üst kısımda bulunan teknik açıklamadan da anlayacağımız üzere asenkron olarak veri çekme veri gönderme gibi işlemler için kullanılan bir middleware’dır. Thunk yapısı,Async isteklerinizi bu middleware ile kullandığınızda Promise’in dönüş durumuna bağlı olarak pending, fulfilled, rejected olarak 3 farkılı dönüş sağlar bize bu dönüşler ;

pending → T anında api ile iletişime geçtiğini gösterir ve genelde loading durumlarında ekranda spinner yada skeleton göstermek için kullanılır.

fulfilled → Get yada post işlemleri için elde edilen verilerde kullanılır örnek olarak bir veri çektiğinizde request işlemi bitip response döndükten sonra state içine verileri vermek için kullanılır.

rejected → Get yada Post işlemleri ya da async olarak gerçekleşen herhangi bir işlem sonucu bir hata mesajı alındığı zaman yürürlüğe girer.

Şimdi beraber bu thunk middleware’ını kullanarak bir ürün listesi oluşturalım.

Kaynak kodlarını github üzerinden Thunk yapısı isimli commit üzerinden ulaşabilirsiniz.

Thunk yapısını kullanma

Şimdi ilk olarak tabikide bir slice’a ihtiyacımız olacak. Öncelikle slice oluşturmaya başlayalım.

Hemen Projemiz içinde bulunan Redux dosyamızın altında bulunan features dosyamızın içine Products adında bir dosya oluşturalım daha sonra ProductSlice adında bir javascript dosyası oluşturalım .

Daha önce oluşturduğumuz slice yapısı ile tamamen aynı şekilde ilerleyeceğiz ufak bir farklılık var middleware için kullanacağımız.

İlk olarak initialstate’i oluşturalım.
Burada daha önce oluşturduğumuz initialState objelerine göre ufak bir farklılığa sahip olmamız gerekiyor pending , fulfilled ve rejected durumlarına göre neler olup bittiğini kullanıcıya yansıtabilmemiz için isLoading ve isError olmak üzere 2 farklı durumu state üzerine eklememiz gerekiyor ki middleware’den tam olarak yararlanabilelim.

const initialState = {
productList: [], //Ürünlerin listeleneceği alan
isLoading: null, //Loading durumuna erişmemiz için
isError: null, //Hata varsa hata ile ilgili duruma erişmemiz için
};

Şimdi slice yapımızı oluşturalım.

export const ProductSlice = createSlice({
name: "Product",
initialState,
reducers: {},
});

Şimdi asenkron olarak veri çekme işlemi için Thunk yapımızı oluşturalım.

Oluşturacağımız bu yapıyı mutlaka Export etmemiz gerekiyor çünkü bu yapıyı kullanarak api call işlemini tetikleyeceğiz.

export const FetchProduct = createAsyncThunk();

Şimdi bu yapı bizden 2 farklı parametre beklemektedir.
Bunlar typePrefix ve payloadCreator.

typePrefix, createAsyncThunk ile oluşturulan üç farklı Redux eylemini birbirinden ayırt etmek için kullanılan bir başlangıç ismidir. Bu başlangıç ismi, üç farklı eylem türünü belirlemek için kullanılır. Bu üç tür:

  1. İşlem başlatma (Pending): Bu, asenkron işlem başladığında gönderilen eylemdir.
  2. İşlem başarıyla tamamlandı (Fulfilled): Bu, asenkron işlem başarıyla tamamlandığında gönderilen eylemdir.
  3. İşlem hata ile sonuçlandı (Rejected): Bu, asenkron işlem bir hatayla sonuçlandığında gönderilen eylemdir.

typePrefix, bu üç eylemin türlerine başlangıç ismi verir. Örneğin, eğer typePrefix "todos/fetch" ise, otomatik olarak aşağıdaki eylem türleri oluşturulur:

  • “todos/fetch/pending” (İşlem başlatma)
  • “todos/fetch/fulfilled” (İşlem başarıyla tamamlandı)
  • “todos/fetch/rejected” (İşlem hata ile sonuçlandı)

Bu şekilde, her bir eylem türü, hangi işlemin başladığını, başarıyla tamamlandığını veya hata ile sonuçlandığını ayırt etmek için kullanılır.

Genelde isimlendirmeleri şu şekilde yapıyorlar ;

“SLİCE_İSMİ” / “Thunk Assign eden Değişken”

export const FetchProduct = createAsyncThunk(
"Product/FetchProduct",
);

Şimdi ikinci parametremiz olan payloadCreator nedir buna bakalım.

payloadCreator: Bu işlev, asenkron işlem sonucu olarak dönen veriyi oluşturur. Daha basit bir diyinle api call işlemini yapan fonksiyon.

const FetchProductAsynFunction = async () => {
const res = await axios("https://fakestoreapi.com/products");
const data = await res.data;
return data;
};

export const FetchProduct = createAsyncThunk(
"Product/FetchProduct",
FetchProductAsynFunction
);


//Başka bir kullanım türü şeklinde (Genelde böyle kullanılır).

export const FetchProduct = createAsyncThunk(
"Product/FetchProduct", async () => {
const res = await axios('https://fakestoreapi.com/products')
const data = await res.data
return data
});

Şimdi Slice ve Thunk yapısını oluşturduk . Artık bu Thunk yapısına göre bahsettiğimiz 3 ana durum (pending,fulfilled ve rejected) için reducer hazırlamamız gerekiyor.

Şimdi bu işlem için öncelikle Slice içinde düzenleme yapmamız gerekmekte. Bir kaç paragraf üstte size ufak bir fark olduğundan bahsetmiştim şimdi o farklılığa geldik.
Reducerları oluştururken burada reducers objesi içinde değil extraReducers parametersini kullanacağız.

export const ProductSlice = createSlice({
// Slice ismi belirtiliyor
name: "Product",
// Başlangıç state'i ayarlanıyor
initialState,
// Reducer ayarlamaları, burada herhangi bir ayarlama yapılmamaktadır.
reducers: {},
// Ekstra reducerler Thunk yapısı için tanımlandı.
extraReducers: (builder) => {
// Ürün detaylarının alınması durumunda çalışacak function
builder.addCase(FetchProduct.pending, (state) => {
// Verinin yüklenmesinin durumu provoke edilir
state.isLoading = true;
});
// Ürünlerin başarılı bir şekilde alınması durumu
builder.addCase(FetchProduct.fulfilled, (state, action) => {
// Yüklenme durumu false olur ve ürünler state'e yerleştirilir.
state.isLoading = false;
state.productList = action.payload;
});
// Ürünlerin alınamaması durumu
builder.addCase(FetchProduct.rejected, (state, action) => {
// Yüklenme durumu false olur ve hata bilgisi state'e yerleştirilir.
state.isLoading = false;
state.isError = action.error.message;
});
},
});

Thunk yapısı yapmamız gereken bütün geliştirmeler bitti. Şimdi bu işlem için bir UI geliştirelim.

FetchProduct → Bizim Asyn Fonksiyonumuz bunu Thunk’ın tetiklenmesi için export etmemiz gerektiğini daha önce size belirmiştim.

Şimdi projemize göz atalım.

Thunk middleware’i hakkında belirmem gereken fakat ben şimdiye kadar hiç kullanıldığı bir projeye açıkçası denk gelmediğim bir konu daha var ,
Thunk fonksiyonu üçüncü bir parametre daha kabul ediyor bu parametre options.İsimden de anlaşılacağını üzere özelleştirme sağlıyor bu parametre ile bizler obje içinde özelleştirmeler yapabiliriz ,
Şimdi ilk olarak kendi oluşturduğumuz Thunk yapısı üzerinden konuları inceleyelim.

export const FetchProduct = createAsyncThunk(
"Product/FetchProduct",
FetchProductAsynFunction,
{condition: .....}
);

1- Condition (isteğe bağlı): Bu işlev, asenkron işlemi başlatmadan önce belirli bir koşulu kontrol etmenizi sağlar. Eğer condition işlevi false dönerse, işlem başlatılmaz. Bu, belirli koşulların sağlanmadığı durumlarda işlemin engellenmesi için kullanışlıdır. Örnek olarak daha önce veriler çekilmiş ise ve state içinde eklenmiş ise tekrar verilerin çekilmesini engellemek için kullanılabilir.

    const FetchProduct = createAsyncThunk(
"Product/FetchProduct",
async () => {
const res = await axios("https://fakestoreapi.com/products");
const data = await res.data;
return data;
},
{
condition: (arg, { getState }) => {
const { products } = getState();
// Eğer ürünler zaten yüklenmişse, tekrar yüklemeyi atla
if (products.length > 0) {
return false;
}
},
}
);

2- DispatchConditionRejection: condition() işlevi false döndürürse, varsayılan davranış hiçbir işlemin gönderilmemesidir. Ancak, işlem iptal edildiğinde hala "reddedilmiş" bir işlemin gönderilmesini istiyorsanız, bu bayrağı true olarak ayarlamak gerekir.

    const FetchProduct = createAsyncThunk(
"Product/FetchProduct",
async () => {
const res = await axios("https://fakestoreapi.com/products");
const data = await res.data;
return data;
},
{
condition: (arg, { getState }) => {
const { products } = getState();
if (products.length > 0) {
return false;
}
},
dispatchConditionRejection: true,
}
);

3- idGenerator(arg) : Bu işlev, istek sırası için requestId oluştururken kullanılır. Varsayılan olarak nanoid kullanır, ancak kendi ID oluşturma mantığınızı uygulayabilirsiniz.

    const FetchProduct = createAsyncThunk(
"Product/FetchProduct",
async () => {
const res = await axios("https://fakestoreapi.com/products");
const data = await res.data;
return data;
},
{
idGenerator: () => "uniqueId", // Kendi ID oluşturma mantığınız
}
);

4- SerializeError: Bu işlev, istek sırası için requestId oluştururken kullanılır. Varsayılan olarak nanoid kullanır, ancak kendi ID oluşturma mantığınızı uygulayabilirsiniz.

    const FetchProduct = createAsyncThunk(
"Product/FetchProduct",
async () => {
const res = await axios("https://fakestoreapi.com/products");
const data = await res.data;
return data;
},
{
// Kendi hata serileştirme mantığınız
serializeError: (error) => error.message,

}
);

5- getPendingMeta: Bu işlev, pendingAction.meta alanına birleştirilecek bir nesne oluşturmak için kullanılır.

    const FetchProduct = createAsyncThunk(
"Product/FetchProduct",
async () => {
const res = await axios("https://fakestoreapi.com/products");
const data = await res.data;
return data;
},
{
getPendingMeta: ({ arg, requestId }, { getState, extra }) => {
// Kendi meta verinizi oluşturun
return {
customMetaKey: "customMetaData",
};
},
}
);

Projemizin Github Linki : https://github.com/kubilaybzk/redux-toolkit-medium

Kaynaklar:
https://akursat.medium.com/redux-toolkit-6a49f5eab539
https://medium.com/@furkansunger/redux-toolkite-giris-953a57860d8c
https://redux-toolkit.js.org/
https://academy.patika.dev/courses/redux

--

--