Kurumsal React Uygulamalarında Zustand ile Durum Yönetimi: Kapsamlı Bir Rehber
Büyük ölçekli React uygulamalarında durum yönetimi, geliştiricilerin sıklıkla karşılaştığı karmaşık bir meydan okumadır. Uygulama büyüdükçe, veri akışını yönetmek, bileşenler arası iletişimi sağlamak ve performansı optimize etmek giderek zorlaşır. Bu durum, geliştirme sürecini yavaşlatabilir, hatalara yol açabilir ve uygulamanın sürdürülebilirliğini olumsuz etkileyebilir. Peki, bu karmaşıklığı basitleştirmenin, geliştirici verimliliğini artırmanın ve aynı zamanda yüksek performans sunmanın bir yolu var mı? İşte tam da bu noktada, hafifliği ve kullanım kolaylığı ile öne çıkan Zustand devreye giriyor. Bu makalede, kurumsal React uygulamalarında Zustand’ın gücünü keşfedecek, temel prensiplerinden ileri düzey kullanım senaryolarına kadar her yönünü detaylıca inceleyeceğiz.
Kurumsal Uygulamalarda Durum Yönetimi Neden Bu Kadar Önemli?
Herhangi bir modern web uygulamasının kalbinde durum (state) yer alır. Durum, uygulamanın belirli bir andaki verilerini ve kullanıcı arayüzünün (UI) görünümünü belirleyen bilgiler bütünüdür. React gibi bileşen tabanlı kütüphanelerde, her bileşenin kendi iç durumu olabilir. Ancak, uygulama büyüdükçe ve farklı bileşenlerin aynı verilere erişmesi veya birbirleriyle iletişim kurması gerektiğinde, bu yerel durum yönetimi yaklaşımı yetersiz kalmaya başlar. İşte bu noktada global durum yönetimi (global state management) ihtiyacı doğar.
Özellikle kurumsal düzeydeki uygulamalarda durum yönetiminin önemi katlanarak artar. Bir e-ticaret uygulamasını düşünün: kullanıcı sepetindeki ürünler, oturum açma bilgileri, sipariş geçmişi, ürün filtreleri ve çok daha fazlası uygulamanın farklı yerlerinde erişilmesi ve güncellenmesi gereken global durum parçalarıdır. Bu verilerin tutarlı, güvenli ve performanslı bir şekilde yönetilmesi, hem kullanıcı deneyimi hem de uygulamanın genel sağlığı için kritik öneme sahiptir.
Geleneksel React yaklaşımlarında, bir veriyi alt bileşenlere aktarmak için prop drilling (prop zincirleme) adı verilen bir yöntem kullanılır. Bu, verinin birçok ara bileşenden geçirilmesi anlamına gelir ve büyük uygulamalarda kodu okunmaz, bakımı zor ve hata yapmaya açık hale getirir. Ayrıca, Context API gibi React’ın kendi yerleşik çözümleri, basit senaryolar için harika olsa da, sık güncellenen ve çok sayıda tüketiciye sahip global durumlar için performans sorunlarına yol açabilir, çünkü bağlam (context) değiştiğinde ona abone olan tüm bileşenler yeniden render (yeniden çizim) edilir.
Durum yönetimi çözümleri, bu sorunlara merkezi bir depo (store) veya mağaza sağlayarak yanıt verir. Bu mağaza, uygulamanın global durumunu tek bir yerde tutar ve bileşenlerin bu duruma doğrudan erişmesini veya güncellemesini sağlar. Bu sayede:
- Veri Akışı Basitleşir: Verilerin nasıl ve nerede güncellendiğini takip etmek kolaylaşır.
- Performans Artar: Akıllı durum yönetim kütüphaneleri, sadece değişen kısımları yeniden render ederek gereksiz güncellemeleri engeller.
- Bakım Kolaylaşır: Kod daha modüler hale gelir, hataların tespiti ve düzeltilmesi hızlanır.
- Test Edilebilirlik Gelişir: Durum mantığı bileşenlerden ayrıldığı için daha kolay test edilebilir.
Piyasada Redux, Recoil, Jotai gibi birçok durum yönetim kütüphanesi bulunmaktadır. Her birinin kendine özgü avantajları ve dezavantajları vardır. Ancak son yıllarda, özellikle basitliği, hafifliği ve performans odaklı yapısıyla öne çıkan Zustand, birçok geliştiricinin dikkatini çekmeyi başarmıştır. Peki, Zustand’ı bu kadar özel kılan nedir ve kurumsal projelerde nasıl bir fark yaratır?
Zustand Nedir ve Onu Diğerlerinden Ayıran Özellikler Nelerdir?
Zustand, React uygulamaları için tasarlanmış, küçük, hızlı ve ölçeklenebilir bir durum yönetim kütüphanesidir. Adını Almanca “durum” anlamına gelen kelimeden alan Zustand, React Hooks (kancalar) üzerine inşa edilmiş minimalist bir API sunar. Geleneksel durum yönetim kütüphanelerinin aksine, Zustand’ın temel felsefesi “basitlik” ve “az kodla çok iş yapma” üzerine kuruludur. Bu sayede, geliştiricilerin karmaşık şablon kodlarla uğraşmak yerine doğrudan iş mantığına odaklanmasına olanak tanır.
Zustand’ı diğer popüler durum yönetim kütüphanelerinden ayıran başlıca özellikler şunlardır:
- Boilerplate (Şablon Kod) Yok Denecek Kadar Az: Redux gibi kütüphaneler, reducer’lar, action type’lar, dispatcher’lar ve middleware’ler gibi birçok kavramı ve dosyayı beraberinde getirir. Zustand ise, tek bir fonksiyon çağrısı ile bir mağaza (store) oluşturmanıza olanak tanır. Bu, özellikle küçük ve orta ölçekli projelerde geliştirme hızını önemli ölçüde artırır.
- React Context’ten Bağımsız Çalışma: Zustand, React Context API’yi dahili olarak kullanmaz. Bu, Context’in doğasından gelen bazı performans sınırlamalarını aşmasına yardımcı olur. Bir Context değeri değiştiğinde, o Context’e abone olan tüm bileşenler yeniden render edilir. Zustand ise, bileşenlerin yalnızca ihtiyaç duydukları durum parçalarına abone olmalarına izin veren akıllı bir seçici mekanizması (selector mechanism) kullanır. Bu sayede, gereksiz yeniden render’lar engellenir ve performans optimize edilir.
- Hook Tabanlı API: Zustand, React’ın modern hook yapısını benimser. Bu, React geliştiricileri için öğrenme eğrisini oldukça kısaltır, çünkü zaten alışkın oldukları bir desenle çalışırlar.
useStoregibi basit bir hook ile mağazanızdaki duruma erişebilir ve onu güncelleyebilirsiniz. - Performans Odaklı: Zustand, durumu güncellemeleri atomik bir şekilde işleyerek ve sadece değişen duruma abone olan bileşenleri yeniden render ederek yüksek performans sağlar. Bu, özellikle büyük ve veri yoğun uygulamalarda fark yaratır.
- Ölçeklenebilirlik: Basit bir API’ye sahip olmasına rağmen, Zustand kurumsal düzeyde uygulamaları destekleyecek şekilde tasarlanmıştır. Middleware’ler (ara yazılımlar) ve modüler mağaza yapılandırmaları sayesinde uygulamanız büyüdükçe durum yönetiminizi kolayca genişletebilirsiniz.
- Immutable (Değişmez) Durum Yönetimi: Zustand, durumun doğrudan değiştirilmemesi, bunun yerine yeni bir durum nesnesi döndürülmesi prensibini benimser. Bu, durum değişikliklerinin izlenmesini kolaylaştırır ve beklenmedik yan etkileri (side effects) azaltır.
Özetle, Zustand, React geliştiricilerine daha az kodla daha fazla esneklik ve performans sunan modern bir durum yönetim çözümü vaat ediyor. Özellikle karmaşık olmayan ancak ölçeklenebilirlik ve performans gerektiren kurumsal projelerde, hızlı prototipleme ve kolay bakım imkanı sunarak değerli bir araç haline geliyor.
Zustand ile İlk Adımlar: Temel Bir Durum Mağazası (Store) Nasıl Oluşturulur?
Zustand ile çalışmaya başlamak oldukça basittir. İlk olarak, projenize Zustand’ı eklemeniz gerekir. Ardından, uygulamanızın global durumunu yönetecek bir mağaza (store) oluşturabilirsiniz. Bu bölümde, basit bir sayaç uygulaması örneği üzerinden Zustand’ın temel kullanımını adım adım inceleyeceğiz.
Kurulum
Projenize Zustand’ı kurmak için aşağıdaki komutlardan birini kullanabilirsiniz:
npm install zustand
veya
yarn add zustand
Basit Bir Sayaç Mağazası Oluşturma
Bir Zustand mağazası, create fonksiyonu kullanılarak oluşturulur. Bu fonksiyon, bir “set” fonksiyonu alan ve durum nesnesini döndüren bir geri arama (callback) fonksiyonu alır. “set” fonksiyonu, mağazanın durumunu güncellemek için kullanılır.
Öncelikle, projenizin kök dizininde veya durum yönetim dosyalarınızı tuttuğunuz bir klasörde (örneğin, src/store/useCounterStore.js) yeni bir dosya oluşturalım:
// src/store/useCounterStore.js
import { create } from 'zustand';
// create fonksiyonu ile bir mağaza oluşturuyoruz.
// Bu mağaza, bir sayaç değeri (count) ve bu değeri arttırma/azaltma aksiyonlarına sahip olacak.
const useCounterStore = create((set) => ({
// Durum (state) tanımlaması
count: 0,
// Aksiyonlar (actions) tanımlaması
// 'set' fonksiyonu, durumun bir sonraki halini belirlemek için kullanılır.
// Önceki durumu (state) alıp yeni bir durum nesnesi döndürürüz.
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }), // Durumu doğrudan bir nesne ile de güncelleyebiliriz
}));
export default useCounterStore;
Yukarıdaki kodda, useCounterStore adında bir hook oluşturduk. Bu hook, count adında bir duruma ve increment, decrement, reset adında üç aksiyona sahiptir. Aksiyonlar, set fonksiyonunu kullanarak durumu günceller. set fonksiyonu, önceki durumu alıp yeni bir durum nesnesi döndürme prensibiyle çalışır, bu da durumun değişmezliğini (immutability) korumamıza yardımcı olur.
Bileşenlerde Mağazayı Kullanma
Şimdi bu mağazayı React bileşenlerimizde nasıl kullanacağımıza bakalım. useCounterStore hook’unu herhangi bir React fonksiyonel bileşeninde çağırarak mağazanın durumuna ve aksiyonlarına erişebiliriz.
// src/components/CounterComponent.jsx
import React from 'react';
import useCounterStore from '../store/useCounterStore'; // Oluşturduğumuz mağazayı içe aktarıyoruz
function CounterComponent() {
// useCounterStore hook'unu kullanarak mağazadaki durumu ve aksiyonları seçiyoruz.
// Zustand'ın en güçlü özelliklerinden biri, sadece ihtiyacınız olan durum parçalarını seçebilmenizdir.
// Bu, yalnızca seçilen durum değiştiğinde bileşenin yeniden render edilmesini sağlar.
const count = useCounterStore((state) => state.count);
const increment = useCounterStore((state) => state.increment);
const decrement = useCounterStore((state) => state.decrement);
const reset = useCounterStore((state) => state.reset);
// Alternatif olarak, tüm mağaza nesnesini de alabilirsiniz, ancak performans için seçici kullanım önerilir:
// const { count, increment, decrement, reset } = useCounterStore();
return (
<div style={{ padding: '20px', border: '1px solid #ccc', borderRadius: '8px', maxWidth: '300px', margin: '20px auto' }}>
<h3>Zustand Sayaç Uygulaması</h3>
<p>Mevcut Sayaç Değeri: <strong>{count}</strong></p>
<button onClick={increment} style={{ margin: '5px', padding: '10px 15px', border: 'none', borderRadius: '4px', cursor: 'pointer' }}>Arttır (+1)</button>
<button onClick={decrement} style={{ margin: '5px', padding: '10px 15px', border: 'none', borderRadius: '4px', cursor: 'pointer' }}>Azalt (-1)</button>
<button onClick={reset} style={{ margin: '5px', padding: '10px 15px', border: 'none', borderRadius: '4px', cursor: 'pointer' }}>Sıfırla</button>
</div>
);
}
export default CounterComponent;
Bu örnekte, useCounterStore hook’unu kullanarak count değerine ve increment, decrement, reset aksiyonlarına erişiyoruz. Mağazadaki count değeri değiştiğinde, sadece bu değeri kullanan CounterComponent yeniden render edilecektir. Bu, Zustand’ın akıllı seçici mekanizması sayesinde elde edilen önemli bir performans avantajıdır.
Bu basit adımlarla, Zustand ile temel bir durum mağazası oluşturmayı ve bunu React bileşenlerinizde kullanmayı öğrendiniz. Bu temel yapı, kurumsal uygulamalarınızdaki daha karmaşık durumları yönetmek için bir başlangıç noktası olacaktır.
Kurumsal Uygulamalar İçin Gelişmiş Zustand Kullanım Senaryoları
Zustand’ın basit API’si, onu sadece temel durum yönetimi için değil, aynı zamanda kurumsal düzeydeki karmaşık senaryolar için de güçlü bir araç haline getirir. Bu bölümde, asenkron işlemlerin yönetimi, middleware’ler (ara yazılımlar) ile durum yönetimini zenginleştirme ve modüler mağaza yapıları oluşturma gibi ileri düzey konuları ele alacağız.
Asenkron İşlemler ve API Çağrıları Nasıl Yönetilir?
Modern uygulamaların çoğu, sunucu tarafıyla iletişim kurmak için asenkron (eşzamansız) API çağrılarına ihtiyaç duyar. Zustand, bu tür işlemleri yönetmek için doğrudan async/await sözdizimini kullanmanıza olanak tanır. Mağazanızdaki aksiyonlar içinde asenkron fonksiyonlar tanımlayabilir ve API çağrılarının farklı aşamalarını (yükleniyor, başarılı, hata) durumunuza yansıtabilirsiniz.
Örnek olarak, bir kullanıcı listesini bir API’den çeken bir mağaza oluşturalım:
// src/store/useUserStore.js
import { create } from 'zustand';
const useUserStore = create((set) => ({
users: [],
loading: false,
error: null,
fetchUsers: async () => {
set({ loading: true, error: null }); // Yükleme başladığında durumu güncelle
try {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
if (!response.ok) {
throw new Error(HTTP hata kodu: ${response.status});
}
const data = await response.json();
set({ users: data, loading: false }); // Başarılı olduğunda kullanıcıları ve yükleme durumunu güncelle
} catch (error) {
set({ error: error.message, loading: false }); // Hata durumunda hatayı ve yükleme durumunu güncelle
}
},
}));
export default useUserStore;
Bu mağazayı bir React bileşeninde kullanmak oldukça basittir:
// src/components/UserList.jsx
import React from 'react';
import useUserStore from '../store/useUserStore';
function UserList() {
const { users, loading, error, fetchUsers } = useUserStore();
if (loading) return <p>Kullanıcılar yükleniyor...</p>;
if (error) return <p style={{ color: 'red' }}>Hata: {error}</p>;
return (
<div style={{ padding: '20px', border: '1px solid #ccc', borderRadius: '8px', maxWidth: '400px', margin: '20px auto' }}>
<h3>Kullanıcı Listesi</h3>
<button onClick={fetchUsers} disabled={loading} style={{ margin: '5px', padding: '10px 15px', border: 'none', borderRadius: '4px', cursor: 'pointer' }}>Kullanıcıları Getir</button>
<ul>
{users.map((user) => (
<li key={user.id}>{user.name} ({user.email})</li>
))}
</ul>
</div>
);
}
export default UserList;
Bu yapı, kurumsal uygulamalarda sıkça karşılaşılan veri çekme işlemlerini temiz ve yönetilebilir bir şekilde ele almanızı sağlar.
Middleware’ler ile Durum Yönetimine Güç Katmak
Zustand, mağaza davranışını genişletmek için middleware’ler kullanmanıza olanak tanır. En popüler middleware’lerden ikisi persist (kalıcılık) ve devtools (geliştirici araçları) middleware’leridir.
Persist Middleware ile Durumu Kalıcı Hale Getirme
persist middleware’i, mağazanızdaki durumu tarayıcının localStorage veya sessionStorage gibi depolama alanlarında kalıcı hale getirmenizi sağlar. Bu, kullanıcı tarayıcıyı kapattığında veya sayfayı yenilediğinde durumun kaybolmamasını sağlar.
// src/store/useAuthStore.js
import { create } from 'zustand';
import { persist, devtools } from 'zustand/middleware';
const useAuthStore = create(
devtools( // Geliştirici araçları entegrasyonu için (opsiyonel)
persist( // Durumu kalıcı hale getirmek için
(set) => ({
authToken: null,
userProfile: null,
login: (token, profile) => set({ authToken: token, userProfile: profile }),
logout: () => set({ authToken: null, userProfile: null }),
}),
{
name: 'auth-storage', // localStorage'da kullanılacak anahtar adı
getStorage: () => localStorage, // Hangi depolama alanının kullanılacağını belirtiyoruz (varsayılan: localStorage)
}
)
)
);
export default useAuthStore;
Bu mağaza, kullanıcının kimlik doğrulama token’ını ve profil bilgilerini tarayıcının yerel depolama alanında saklayacaktır. Uygulama yeniden yüklendiğinde, durum otomatik olarak geri yüklenecektir.
Devtools Middleware ile Geliştirici Araçları Entegrasyonu
devtools middleware’i, mağazanızı Redux DevTools uzantısı ile entegre etmenizi sağlar. Bu, durum değişikliklerini izlemek, aksiyonları görmek ve zaman yolculuğu hata ayıklaması yapmak için paha biçilmez bir araçtır.
Yukarıdaki useAuthStore örneğinde gördüğünüz gibi, devtools middleware’i create fonksiyonunun içinde persist middleware’ini saracak şekilde kullanılabilir.
Birden Fazla Mağazayı Yönetme ve Modüler Yapı
Büyük kurumsal uygulamalarda, tüm global durumu tek bir mağazada tutmak karmaşıklığa yol açabilir. Zustand, farklı işlevsel alanlar için birden fazla mağaza oluşturarak modüler bir yapı kurmanıza olanak tanır. Örneğin, bir e-ticaret uygulamasında ayrı bir sepet mağazası, bir kullanıcı mağazası ve bir ürün filtre mağazası olabilir.
Bu mağazaların her biri bağımsız olarak çalışır, ancak gerektiğinde birbirlerinin durumuna erişebilir veya birbirlerinin aksiyonlarını çağırabilirler. Bu, kodunuzu daha düzenli, bakımı kolay ve ölçeklenebilir hale getirir.
Mağazalar arası iletişim genellikle basit bir import/export mekanizmasıyla sağlanır. Örneğin, bir sepet mağazası, kullanıcı mağazasından kullanıcının oturum durumunu kontrol etmek isteyebilir.
// src/store/useCartStore.js
import { create } from 'zustand';
import useAuthStore from './useAuthStore'; // Auth mağazasını içe aktarıyoruz
const useCartStore = create((set, get) => ({
items: [],
addItem: (product) => {
// Sepete ürün eklemeden önce kullanıcının oturum açmış olup olmadığını kontrol edebiliriz
const { authToken } = useAuthStore.getState(); // Başka bir mağazanın durumuna doğrudan erişim
if (!authToken) {
console.warn('Ürün eklemek için lütfen giriş yapın.');
// Kullanıcıya bildirim gösterebilir veya giriş sayfasına yönlendirebiliriz
return;
}
set((state) => ({ items: [...state.items, product] }));
},
// ... diğer sepet aksiyonları
}));
export default useCartStore;
Bu yaklaşımla, her mağaza kendi sorumluluk alanına odaklanırken, gerektiğinde diğer mağazalarla etkileşime geçebilir. Bu modüler yapı, kurumsal uygulamaların karmaşıklığını yönetmek için hayati öneme sahiptir.
Gerçek Dünya Senaryosu: E-ticaret Uygulaması Sepet Yönetimi
Şimdiye kadar Zustand’ın temel ve ileri düzey özelliklerini inceledik. Bu bölümde, öğrendiklerimizi bir araya getirerek gerçek dünyadan bir senaryoyu ele alacağız: orta ölçekli bir e-ticaret uygulamasında sepet yönetimi. Bu vaka analizi, Zustand’ın karmaşık iş mantığını nasıl basit ve etkili bir şekilde yönetebileceğini gösterecektir.
Vaka Analizi: E-ticaret Sepet Yönetimi Gereksinimleri
Bir e-ticaret uygulamasında sepet yönetimi genellikle aşağıdaki gereksinimleri içerir:
- Ürünleri sepete ekleme (mevcutsa miktarını artırma).
- Sepetten ürün çıkarma.
- Sepetteki bir ürünün miktarını güncelleme.
- Sepeti tamamen boşaltma.
- Sepetteki toplam ürün sayısını ve toplam fiyatı dinamik olarak hesaplama.
- Kullanıcı sayfayı yenilediğinde veya tarayıcıyı kapattığında sepet içeriğinin kaybolmaması (kalıcılık).
Zustand ile Sepet Mağazası Modellemesi
Bu gereksinimleri karşılamak için Zustand ile useCartStore adında bir mağaza oluşturalım. Bu mağaza, sepet içeriğini (items), toplam ürün sayısını ve toplam fiyatı yönetecek aksiyonları içerecektir. Kalıcılık için persist middleware’ini de kullanacağız.
// src/store/useCartStore.js
import { create } from 'zustand';
import { persist, devtools } from 'zustand/middleware';
const useCartStore = create(
devtools(
persist(
(set, get) => ({
items: [], // Sepetteki ürünler
// Ürün ekleme aksiyonu
addItem: (product) => set((state) => {
const existingItemIndex = state.items.findIndex(item => item.id === product.id);
if (existingItemIndex > -1) {
// Ürün zaten sepette varsa miktarını artır
const newItems = [...state.items];
newItems[existingItemIndex] = {
...newItems[existingItemIndex],
quantity: newItems[existingItemIndex].quantity + 1,
};
return { items: newItems };
}
// Ürün sepette yoksa yeni olarak ekle
return { items: [...state.items, { ...product, quantity: 1 }] };
}),
// Ürün çıkarma aksiyonu
removeItem: (productId) => set((state) => ({
items: state.items.filter(item => item.id !== productId),
})),
// Ürün miktarını güncelleme aksiyonu
updateQuantity: (productId, quantity) => set((state) => ({
items: state.items.map(item =>
item.id === productId ? { ...item, quantity: Math.max(1, quantity) } : item // Miktar en az 1 olmalı
),
})),
// Sepeti boşaltma aksiyonu
clearCart: () => set({ items: [] }),
// Toplam ürün sayısını hesaplayan bir seçici (selector)
// get() fonksiyonunu kullanarak güncel duruma erişebiliriz
getTotalItems: () => get().items.reduce((total, item) => total + item.quantity, 0),
// Toplam fiyatı hesaplayan bir seçici
getTotalPrice: () => get().items.reduce((total, item) => total + item.price * item.quantity, 0),
}),
{
name: 'e-commerce-cart', // localStorage'da kullanılacak anahtar
getStorage: () => localStorage, // localStorage kullan
}
)
)
);
export default useCartStore;
Sepet Bileşeninde Kullanım
Şimdi bu mağazayı kullanarak bir sepet bileşeni (CartComponent) oluşturalım. Bu bileşen, sepet içeriğini gösterecek, ürün ekleme/çıkarma/miktar güncelleme düğmelerine sahip olacak ve toplam bilgileri görüntüleyecektir.
// src/components/CartComponent.jsx
import React from 'react';
import useCartStore from '../store/useCartStore';
function CartComponent() {
const { items, addItem, removeItem, updateQuantity, clearCart } = useCartStore();
// Seçicileri kullanarak sadece ihtiyacımız olan türetilmiş durumu alıyoruz
const totalItems = useCartStore((state) => state.getTotalItems());
const totalPrice = useCartStore((state) => state.getTotalPrice());
// Örnek bir ürün nesnesi
const sampleProduct1 = { id: 101, name: 'Akıllı Telefon', price: 5000 };
const sampleProduct2 = { id: 102, name: 'Kablosuz Kulaklık', price: 1500 };
return (
<div style={{ padding: '20px', border: '1px solid #007bff', borderRadius: '8px', maxWidth: '600px', margin: '20px auto', backgroundColor: '#e9f7ff' }}>
<h3>Alışveriş Sepetiniz</h3>
<div style={{ marginBottom: '15px' }}>
<button onClick={() => addItem(sampleProduct1)} style={{ margin: '5px', padding: '10px 15px', border: 'none', borderRadius: '4px', cursor: 'pointer', backgroundColor: '#28a745', color: 'white' }}>Akıllı Telefon Ekle</button>
<button onClick={() => addItem(sampleProduct2)} style={{ margin: '5px', padding: '10px 15px', border: 'none', borderRadius: '4px', cursor: 'pointer', backgroundColor: '#28a745', color: 'white' }}>Kulaklık Ekle</button>
<button onClick={clearCart} style={{ margin: '5px', padding: '10px 15px', border: 'none', borderRadius: '4px', cursor: 'pointer', backgroundColor: '#dc3545', color: 'white' }}>Sepeti Boşalt</button>
</div>
{items.length === 0 ? (
<p>Sepetinizde ürün bulunmamaktadır.</p>
) : (
<ul style={{ listStyleType: 'none', padding: 0 }}>
{items.map((item) => (
<li key={item.id} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '10px 0', borderBottom: '1px dashed #b3d9ff' }}>
<span>{item.name} ({item.quantity} adet) - <strong>{item.price * item.quantity} TL</strong></span>
<div>
<button onClick={() => updateQuantity(item.id, item.quantity + 1)} style={{ margin: '0 5px', padding: '5px 10px', border: 'none', borderRadius: '3px', cursor: 'pointer', backgroundColor: '#007bff', color: 'white' }}>+</button>
<button onClick={() => updateQuantity(item.id, Math.max(1, item.quantity - 1))} style={{ margin: '0 5px', padding: '5px 10px', border: 'none', borderRadius: '3px', cursor: 'pointer', backgroundColor: '#007bff', color: 'white' }}>-</button>
<button onClick={() => removeItem(item.id)} style={{ margin: '0 5px', padding: '5px 10px', border: 'none', borderRadius: '3px', cursor: 'pointer', backgroundColor: '#6c757d', color: 'white' }}>Kaldır</button>
</div>
</li>
))}
</ul>
)}
<div style={{ marginTop: '20px', paddingTop: '15px', borderTop: '2px solid #007bff', display: 'flex', justifyContent: 'space-between', fontWeight: 'bold' }}>
<p>Toplam Ürün Sayısı: {totalItems}</p>
<p>Toplam Tutar: {totalPrice} TL</p>
</div>
</div>
);
}
export default CartComponent;
Bu örnek, Zustand’ın kurumsal bir e-ticaret uygulamasında sepet yönetimi gibi karmaşık bir işlevselliği ne kadar temiz ve okunabilir bir şekilde ele alabildiğini göstermektedir. persist middleware sayesinde, kullanıcı sayfayı yenilese bile sepet içeriği korunur. Seçiciler (getTotalItems, getTotalPrice) sayesinde ise sadece türetilmiş değerler değiştiğinde bileşen yeniden render edilir, bu da performansı artırır. Zustand’ın bu esnekliği ve basitliği, geliştiricilerin iş mantığına odaklanmasını sağlayarak verimliliği artırır.
Zustand ile Performans Optimizasyonları ve En İyi Uygulamalar Nelerdir?
Zustand, doğası gereği performans odaklı bir kütüphane olsa da, kurumsal uygulamalarda en iyi performansı elde etmek için bazı en iyi uygulamaları ve optimizasyon tekniklerini bilmek önemlidir. Bu bölümde, Zustand ile çalışırken uygulayabileceğiniz başlıca performans ipuçlarına ve en iyi pratiklere değineceğiz.
Seçici Abone Olma (Selector Pattern)
Zustand’ın en önemli performans özelliklerinden biri, bileşenlerin mağazanın sadece belirli parçalarına abone olabilmesidir. Eğer bir bileşen tüm mağaza durumunu çekip ardından içindeki bir değeri kullanırsa (const { count } = useCounterStore(); gibi), mağazadaki herhangi bir değişiklik bu bileşenin yeniden render edilmesine neden olabilir. Ancak, sadece ihtiyaç duyulan durumu seçerek bu gereksiz yeniden render’ları engelleyebiliriz.
// Önerilen: Sadece ihtiyacınız olan durumu seçin
const count = useCounterStore((state) => state.count);
// Önerilmeyen: Tüm store'u alıp destructuring yapmak
// const { count, increment } = useCounterStore();
// Bu durumda, 'increment' fonksiyonu değişmese bile 'count' değiştiğinde bileşen yeniden render edilir.
// Ancak, Zustand'ın kendisi fonksiyonları referans eşitliği ile kontrol ettiği için bu yaklaşım da genellikle sorun yaratmaz.
// En güvenli ve performanslı yol, her bir durumu veya aksiyonu ayrı ayrı seçmektir.
const increment = useCounterStore((state) => state.increment); // Aksiyonları da seçici olarak almak
Bu “seçici” yaklaşım, Zustand’ın React Context’e göre daha iyi performans sunmasının ana nedenlerinden biridir. Seçici fonksiyonunuzun döndürdüğü değer değişmediği sürece bileşeniniz yeniden render edilmez.
Durumun Normalleştirilmesi (State Normalization)
Büyük ve karmaşık nesneleri veya iç içe geçmiş dizileri doğrudan durum olarak saklamak, durum güncellemelerini ve seçici kullanımı zorlaştırabilir. Durumun normalleştirilmesi, verileri düz (flat) bir yapıya dönüştürerek ve ilişkili verileri ID’leri üzerinden referans göstererek bu sorunu çözer. Bu, genellikle bir veritabanı yapısına benzer şekilde verilerin saklanması anlamına gelir (örneğin, tüm kullanıcıları bir nesnede ID’lerine göre tutmak yerine, bir dizi içinde tutmak).
Normalleştirilmiş durum, güncellemeleri daha verimli hale getirir ve değişmezlik prensibini korumayı kolaylaştırır. Örneğin, bir ürün listesini bir dizi olarak tutmak yerine, ürünleri bir nesne içinde ID’lerine göre saklayabilir ve sepetinizde sadece bu ID’lere referans verebilirsiniz.
Immutable (Değişmez) Durum Yönetimi
Zustand, durumun doğrudan değiştirilmesi yerine, her güncellemede yeni bir durum nesnesi döndürülmesi prensibini teşvik eder. Bu, React’ın ve diğer durum yönetim kütüphanelerinin performans optimizasyonlarını tetiklemesi için kritik öneme sahiptir. Durum doğrudan değiştirilirse (mutasyon), React değişiklikleri algılayamayabilir ve bileşenleri gereksiz yere yeniden render etmeyebilir veya tam tersi, beklenmedik yan etkilere yol açabilir.
// Doğru: Yeni bir nesne döndürerek durumu güncelleme
set((state) => ({ count: state.count + 1 }));
// Yanlış: Durumu doğrudan değiştirme (kaçınılmalıdır)
// set((state) => {
// state.count++; // DOĞRUDAN DEĞİŞİKLİK
// return state;
// });
Karmaşık nesnelerle çalışırken, immer gibi kütüphaneleri Zustand ile entegre ederek daha kolay bir şekilde değişmez güncellemeler yapabilirsiniz. Ancak Zustand’ın kendi set fonksiyonu, bu prensibi zaten doğal olarak destekler.
Mağazaları Modüler Tutmak
Uygulamanız büyüdükçe, tüm durumu tek bir devasa mağazada toplamak yerine, işlevsel alanlara göre birden fazla küçük ve odaklanmış mağaza oluşturun. Örneğin, kimlik doğrulama için bir mağaza, sepet için başka bir mağaza, kullanıcı ayarları için üçüncü bir mağaza. Bu modüler yapı:
- Kodun okunabilirliğini ve bakımını artırır.
- Farklı ekiplerin farklı mağazalar üzerinde bağımsız çalışmasını sağlar.
- Her mağazanın daha küçük ve yönetilebilir olmasını sağlar, bu da hata ayıklamayı kolaylaştırır.
Bu en iyi uygulamaları takip ederek, Zustand ile kurumsal React uygulamalarınızda hem güçlü hem de yüksek performanslı bir durum yönetimi çözümü oluşturabilirsiniz.
Sonuç
Kurumsal React uygulamalarında durum yönetimi, projenin ölçeği büyüdükçe kaçınılmaz bir karmaşıklık kaynağı haline gelebilir. Ancak bu makalede gördüğümüz gibi, Zustand gibi modern ve minimalist bir kütüphane, bu zorlukların üstesinden gelmek için zarif ve etkili bir çözüm sunar. Zustand, basit API’si, az şablon kodu gerektirmesi, React Context’ten bağımsız çalışarak sağladığı performans avantajları ve esnek middleware sistemi sayesinde geliştiricilerin iş mantığına odaklanmasını sağlar.
Temel bir sayaç uygulamasından e-ticaret sepet yönetimi gibi gerçek dünya senaryolarına kadar, Zustand’ın çeşitli kullanım alanlarını ve asenkron işlemler, kalıcılık gibi ileri düzey özelliklerini ele aldık. Ayrıca, seçici abone olma, durum normalleştirme ve değişmezlik prensibi gibi performans optimizasyon tekniklerinin önemini vurguladık. Zustand’ın bu özellikleri, onu hem başlangıç seviyesindeki geliştiriciler hem de deneyimli ekipler için cazip bir seçenek haline getirmektedir.
Geliştirici deneyimini ön planda tutan ve performans beklentilerini karşılayan Zustand, React ekosisteminde kendine sağlam bir yer edinmiştir. Uygulamanızın karmaşıklığı ne olursa olsun, Zustand size temiz, ölçeklenebilir ve bakımı kolay bir durum yönetim mimarisi oluşturma gücü verir. Eğer henüz denemediyseniz, projenizde Zustand’ı kullanarak geliştirme sürecinizi ve uygulamanızın performansını nasıl iyileştirebileceğinizi keşfetmenizi şiddetle tavsiye ederiz.
Sıkça Sorulan Sorular (SSS)
- Zustand Redux’a göre ne gibi avantajlar sunar?
- Zustand, Redux’a kıyasla çok daha az şablon kod (boilerplate) gerektirir, daha basit bir API’ye sahiptir ve öğrenme eğrisi daha kısadır. Ayrıca, Redux’un aksine React Context’ten bağımsız çalışır ve performans optimizasyonları sayesinde gereksiz yeniden render’ları daha etkili bir şekilde engeller. Bu da genellikle daha hafif ve hızlı uygulamalar anlamına gelir.
- Zustand’ı her projede kullanmalı mıyım?
- Küçük ölçekli projeler veya çok az global duruma sahip uygulamalar için React’ın kendi
useStateveuseContexthook’ları yeterli olabilir. Ancak uygulamanız orta veya büyük ölçekli ise, birçok bileşenin aynı duruma erişmesi gerekiyorsa veya durumunuz karmaşıklaşıyorsa, Zustand karmaşıklığı azaltır, veri akışını düzenler ve performansı artırarak geliştirici deneyiminizi önemli ölçüde iyileştirebilir. - Zustand ile TypeScript kullanımı nasıl olur?
- Zustand, TypeScript ile tam uyumludur ve mağazalarınızı tip güvenli bir şekilde oluşturmanıza olanak tanır.
createfonksiyonuna generic tipler tanımlayarak durumunuzun ve aksiyonlarınızın tiplerini belirleyebilirsiniz. Bu, geliştirme sırasında hataları yakalamanıza yardımcı olur ve kodunuzun bakımı ile okunabilirliğini artırır. - Zustand’da persist (kalıcılık) özelliğini nasıl kullanırım?
- Durumunuzu kalıcı hale getirmek için
zustand/middlewarepaketindenpersistmiddleware’ini içe aktarmanız vecreatefonksiyonunu onunla sarmanız yeterlidir.persistfonksiyonuna bir isim (localStorage anahtarı için) ve isteğe bağlı olarak kullanılacak depolama mekanizmasını (localStorage,sessionStoragevb.) belirten bir seçenek nesnesi geçirebilirsiniz. Bu sayede, kullanıcı tarayıcıyı kapattığında veya sayfayı yenilediğinde durumunuz korunur. - Zustand’da birden fazla mağaza arasında iletişim nasıl sağlanır?
- Zustand, mağazalar arasında doğrudan import/export mekanizmasıyla iletişim kurmanıza olanak tanır. Bir mağaza, diğer bir mağazanın hook’unu içe aktararak (örneğin,
import useAuthStore from './useAuthStore';) onun durumuna veya aksiyonlarına erişebilir. Özellikleget()fonksiyonunu kullanarak diğer mağazaların anlık durumunu alabilir veya aksiyonlarını çağırabilirsiniz. Bu, modüler ve ayrık mağazalar arasında temiz bir iletişim yolu sağlar.
#React #Zustand #DurumYönetimi #EnterpriseReact #WebGeliştirme #JavaScript #Frontend