İçeriğe geç
Sedat Demir
Geri dön

Zustand Neden Redux'u Geçti? 57K Yıldızlı Minimal State Yönetimi Devrimi

Zustand Neden Redux'u Geçti? 57K Yıldızlı Minimal State Yönetimi Devrimi

React ekosisteminde state yönetimi her zaman en çok tartışılan konulardan biri olmuştur. Yıllarca Redux bu alanın tartışılmaz kralıydı. Ancak 2024-2025 itibarıyla tabloda ciddi bir değişim yaşanıyor: Zustand, npm indirme sayıları ve geliştirici memnuniyetinde Redux'u geride bırakmaya başladı. Peki 3KB'lık bu minik kütüphane, devasa bir ekosisteme sahip Redux'u nasıl tahtından etti?

Bu yazıda Zustand'ın yükselişini, Redux ile karşılaştırmasını ve neden modern React projelerinde ilk tercih haline geldiğini detaylıca inceleyeceğiz.


State Yönetimi Sorunu: Nereden Geldik?

React'in kendi useState ve useContext hook'ları küçük uygulamalar için yeterlidir. Ancak uygulama büyüdükçe şu sorunlarla karşılaşırız:

Redux bu sorunları çözmek için 2015'te sahneye çıktı ve yıllarca standart oldu. Ancak beraberinde kendi sorunlarını da getirdi.


Redux'un Sorunlu Mirası

Redux'u bir projede kullanmak istediğinizde minimum düzeyde şunlara ihtiyacınız var:

// 1. Action Types
const INCREMENT = 'counter/INCREMENT';
const DECREMENT = 'counter/DECREMENT';

// 2. Action Creators
const increment = () => ({ type: INCREMENT });
const decrement = () => ({ type: DECREMENT });

// 3. Reducer
const counterReducer = (state = { count: 0 }, action) => {
  switch (action.type) {
    case INCREMENT:
      return { ...state, count: state.count + 1 };
    case DECREMENT:
      return { ...state, count: state.count - 1 };
    default:
      return state;
  }
};

// 4. Store
import { createStore } from 'redux';
const store = createStore(counterReducer);

// 5. Provider ile sarmalama
import { Provider } from 'react-redux';
function App() {
  return (
    <Provider store={store}>
      <Counter />
    </Provider>
  );
}

// 6. Bileşende kullanım
import { useSelector, useDispatch } from 'react-redux';
function Counter() {
  const count = useSelector((state) => state.count);
  const dispatch = useDispatch();

  return (
    <div>
      <span>{count}</span>
      <button onClick={() => dispatch(increment())}>+</button>
      <button onClick={() => dispatch(decrement())}>-</button>
    </div>
  );
}

Basit bir sayaç için 6 farklı kavram, en az 3 ayrı dosya ve onlarca satır kod. Redux Toolkit bu durumu iyileştirdi, ancak temel karmaşıklık hâlâ mevcuttur:

// Redux Toolkit ile
import { createSlice, configureStore } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: { count: 0 },
  reducers: {
    increment: (state) => { state.count += 1; },
    decrement: (state) => { state.count -= 1; },
  },
});

export const { increment, decrement } = counterSlice.actions;
const store = configureStore({ reducer: counterSlice.reducer });

Daha iyi, ama hâlâ slice, action, reducer, store, Provider ve selector kavramlarını bilmeniz gerekiyor.


Zustand: "Ayı" Gibi Güçlü, "Tüy" Gibi Hafif

Zustand, Almanca'da "durum" (state) anlamına gelir. Jotai ve Valtio'nun da yaratıcısı olan Daishi Kato ve Poimandres ekibi tarafından geliştirilmiştir.

Aynı sayaç örneğini Zustand ile yazalım:

import { create } from 'zustand';

// Store oluştur — hepsi bu
const useCounterStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
}));

// Bileşende kullan — Provider yok, dispatch yok
function Counter() {
  const count = useCounterStore((state) => state.count);
  const increment = useCounterStore((state) => state.increment);
  const decrement = useCounterStore((state) => state.decrement);

  return (
    <div>
      <span>{count}</span>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  );
}

Hiç Provider yok. Hiç action type yok. Hiç reducer yok. Hiç dispatch yok.

Tek bir create fonksiyonu çağrısıyla store oluşturuyorsunuz ve dönen hook'u doğrudan bileşenlerinizde kullanıyorsunuz. İşte bu kadar.


Zustand'ın Redux'u Geçmesinin 7 Temel Nedeni

1. Sıfır Boilerplate

Yukarıdaki örnekte gördüğünüz gibi, Zustand ile bir store oluşturmak tek bir fonksiyon çağrısıdır. Redux'un action → dispatch → reducer → store döngüsü tamamen ortadan kalkar.

2. Provider Gerektirmez

Redux'un <Provider> bileşeni ile uygulamanızı sarmanız gerekir. Zustand store'ları modül seviyesinde yaşar ve React ağacından bağımsızdır:

// store.js - React'ten tamamen bağımsız
const useStore = create((set) => ({
  user: null,
  setUser: (user) => set({ user }),
}));

// Herhangi bir bileşende, herhangi bir yerde import et ve kullan
import { useStore } from './store';

Bu özellik özellikle micro-frontend mimarilerinde ve test yazarken büyük avantaj sağlar.

3. Otomatik Render Optimizasyonu

Zustand, selector pattern ile sadece değişen state parçasını izler. Gereksiz render'ları otomatik olarak engeller:

// ✅ Sadece "count" değiştiğinde render olur
const count = useCounterStore((state) => state.count);

// ✅ Sadece "user.name" değiştiğinde render olur
const userName = useStore((state) => state.user.name);

// ⚠️ Shallow equality ile birden fazla değer seçme
import { useShallow } from 'zustand/react/shallow';

const { count, increment } = useCounterStore(
  useShallow((state) => ({
    count: state.count,
    increment: state.increment,
  }))
);

4. Middleware Desteği — Tanıdık Ama Daha Basit

Zustand, Redux'un middleware konseptini çok daha zarif bir şekilde sunar:

import { create } from 'zustand';
import { devtools, persist, subscribeWithSelector } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';

const useStore = create(
  devtools(
    persist(
      immer((set) => ({
        todos: [],
        addTodo: (todo) =>
          set((state) => {
            state.todos.push(todo); // Immer ile doğrudan mutasyon!
          }),
        removeTodo: (id) =>
          set((state) => {
            state.todos = state.todos.filter((t) => t.id !== id);
          }),
      })),
      { name: 'todo-storage' } // localStorage'a otomatik kayıt
    ),
    { name: 'TodoStore' } // Redux DevTools'da görünecek isim
  )
);

Bu tek store tanımıyla şunları elde ediyorsunuz:

5. Async İşlemler Doğal Olarak Desteklenir

Redux'ta async işlemler için redux-thunk veya redux-saga gibi ek kütüphanelere ihtiyaç vardır. Zustand'da async fonksiyonlar doğal olarak çalışır:

const useProductStore = create((set, get) => ({
  products: [],
  loading: false,
  error: null,

  fetchProducts: async () => {
    set({ loading: true, error: null });
    try {
      const response = await fetch('/api/products');
      const products = await response.json();
      set({ products, loading: false });
    } catch (error) {
      set({ error: error.message, loading: false });
    }
  },

  getProductById: (id) => {
    // get() ile mevcut state'e erişim
    return get().products.find((p) => p.id === id);
  },
}));

// Bileşende kullanım
function ProductList() {
  const { products, loading, fetchProducts } = useProductStore(
    useShallow((s) => ({
      products: s.products,
      loading: s.loading,
      fetchProducts: s.fetchProducts,
    }))
  );

  useEffect(() => {
    fetchProducts();
  }, [fetchProducts]);

  if (loading) return <Spinner />;
  return products.map((p) => <ProductCard key={p.id} product={p} />);
}

Hiçbir middleware, thunk veya saga gerektirmeden temiz async state yönetimi.

6. React Dışında da Kullanılabilir

Zustand store'ları React hook'u olarak kullanılabildiği gibi, vanilla JavaScript'te de çalışır:

const useStore = create((set) => ({
  theme: 'light',
  toggleTheme: () =>
    set((state) => ({
      theme: state.theme === 'light' ? 'dark' : 'light',
    })),
}));

// React dışında — örneğin bir utility fonksiyonunda
const currentTheme = useStore.getState().theme;

// State değişikliklerini dinle
const unsubscribe = useStore.subscribe((state) => {
  document.body.className = state.theme;
});

// State'i dışarıdan güncelle
useStore.getState().toggleTheme();

Bu özellik, framework-agnostic kütüphaneler yazarken veya React dışı entegrasyonlarda (analytics, web workers vb.) çok değerlidir.

7. TypeScript ile Mükemmel Uyum

Zustand'ın TypeScript desteği birinci sınıftır ve minimum tip tanımı gerektirir:

interface AuthState {
  user: User | null;
  token: string | null;
  isAuthenticated: boolean;
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
}

const useAuthStore = create<AuthState>()((set) => ({
  user: null,
  token: null,
  isAuthenticated: false,

  login: async (email, password) => {
    const { user, token } = await authApi.login(email, password);
    set({ user, token, isAuthenticated: true });
  },

  logout: () => {
    set({ user: null, token: null, isAuthenticated: false });
  },
}));

// Tam tip güvenliği ile kullanım
const user = useAuthStore((s) => s.user); // User | null — otomatik tip çıkarımı

Store Tasarım Desenleri: Gerçek Dünya Örnekleri

Slice Pattern — Büyük Uygulamalar İçin

Büyük uygulamalarda store'u slice'lara ayırabilirsiniz:

// slices/authSlice.ts
export const createAuthSlice = (set, get) => ({
  user: null,
  login: async (credentials) => {
    const user = await api.login(credentials);
    set({ user });
  },
  logout: () => set({ user: null }),
});

// slices/cartSlice.ts
export const createCartSlice = (set, get) => ({
  items: [],
  addItem: (item) =>
    set((state) => ({ items: [...state.items, item] })),
  getTotalPrice: () =>
    get().items.reduce((sum, item) => sum + item.price, 0),
});

// store.ts — slice'ları birleştir
const useBoundStore = create((...args) => ({
  ...createAuthSlice(...args),
  ...createCartSlice(...args),
}));

Computed Values (Türetilmiş Değerler)

const useStore = create((set, get) => ({
  items: [],
  filter: 'all',

  // Türetilmiş değer getter ile
  getFilteredItems: () => {
    const { items, filter } = get();
    if (filter === 'all') return items;
    return items.filter((item) => item.status === filter);
  },

  getItemCount: () => get().items.length,
}));

Sayılarla Zustand vs Redux (2025)

Metrik Zustand Redux (Core) Redux Toolkit
GitHub Yıldızı ~57K ~61K ~10K
Bundle Boyutu ~3KB (gzip) ~7KB (gzip) ~30KB (gzip)
Haftalık npm İndirme ~13M ~10M ~11M
Boilerplate Minimal Yüksek Orta
Öğrenme Eğrisi Düşük Yüksek Orta
Provider Gereksinimi Hayır Evet Evet
Built-in Async Evet Hayır Kısmen
DevTools Middleware ile Built-in Built-in

Not: npm indirme sayılarında Zustand, 2024 ortasından itibaren Redux core'u geçmiştir ve aradaki fark her ay artmaktadır.


Ne Zaman Hâlâ Redux Tercih Edilmeli?

Zustand her senaryo için gümüş kurşun değildir. Şu durumlarda Redux hâlâ mantıklı olabilir:

Ancak yeni bir projeye başlıyorsanız, Zustand neredeyse her zaman daha iyi bir başlangıç noktasıdır.


Hızlı Başlangıç: 2 Dakikada Zustand

npm install zustand
// store.js
import { create } from 'zustand';

export const useAppStore = create((set) => ({
  count: 0,
  increment: () => set((s) => ({ count: s.count + 1 })),
}));

// App.jsx
import { useAppStore } from './store';

export default function App() {
  const count = useAppStore((s) => s.count);
  const increment = useAppStore((s) => s.increment);
  return <button onClick={increment}>Sayaç: {count}</button>;
}

Gerçekten bu kadar. Provider yok, setup yok, konfigürasyon yok.


Sonuç

Zustand'ın 57.000+ GitHub yıldızına ulaşması ve npm indirmelerinde Redux'u geçmesi bir tesadüf değil. Bunun arkasında çok net bir felsefe var: "State yönetimi bu kadar zor olmak zorunda değil."

Redux, kendi döneminde büyük bir boşluğu doldurdu ve React ekosisteminin olgunlaşmasına katkıda bulundu. Ancak React'in hook'larla evrildiği, basitliğin ve geliştirici deneyiminin ön plana çıktığı bu çağda, Zustand tam olarak geliştiricilerin ihtiyaç duyduğu şeyi sunuyor:

Eğer hâlâ Redux ile yeni projeler başlatıyorsanız, kendinize bir iyilik yapın ve Zustand'a bir şans verin. İlk store'unuzu oluşturduğunuzda, "Bu kadar basit olabilir mi?" diye sorduğunuz an, Zustand'ın neden bu kadar popüler olduğunu anlayacaksınız.

State yönetimi zor olmak zorunda değil. Zustand bunu kanıtladı.


Share this post on:

Sonraki Yazı
TanStack Start ile Type-Safe Full-Stack React Uygulamaları Geliştirme