logo
eng-flag

Redux Cheatsheet

İçindekiler

  1. Temel Kavramlar
  2. Eylemler (Actions)
  3. Azaltıcılar (Reducers)
  4. Depo (Store)
  5. Ara Yazılım (Middleware)
  6. React-Redux
  7. Redux Toolkit
  8. Redux Toolkit Örnek Uygulama
  9. En İyi Uygulamalar
  10. İleri Düzey Kavramlar

Temel Kavramlar

Redux, JavaScript uygulamaları için öngörülebilir bir durum konteynerıdır. Tutarlı davranan, farklı ortamlarda çalışan ve test edilmesi kolay uygulamalar yazmanıza yardımcı olur.

Redux'ın üç temel prensibi:

  1. Tek bir gerçek kaynağı
  2. Durum salt okunurdur
  3. Değişiklikler saf fonksiyonlarla yapılır

Eylemler (Actions)

Eylemler, type alanına sahip düz JavaScript nesneleridir. Uygulamada ne olduğunu tanımlarlar.

Örnek:

const addTodo = (text) => {
  return {
    type: 'ADD_TODO',
    payload: text
  }
}

Azaltıcılar (Reducers)

Azaltıcılar, uygulamanın durumunun eylemlere nasıl yanıt vererek değiştiğini belirtir. Önceki durumu ve bir eylemi alan ve bir sonraki durumu döndüren saf fonksiyonlardır.

Örnek:

const initialState = {
  todos: []
}

function todoReducer(state = initialState, action) {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        ...state,
        todos: [...state.todos, action.payload]
      }
    default:
      return state
  }
}

Depo (Store)

Depo, eylemleri ve azaltıcıları bir araya getiren nesnedir. Aşağıdaki sorumlulukları vardır:

  • Uygulama durumunu tutar
  • getState() ile duruma erişim sağlar
  • dispatch(action) ile durumun güncellenmesine izin verir
  • subscribe(listener) ile dinleyicileri kaydeder

Örnek:

import { createStore } from 'redux'
import todoReducer from './reducers'

const store = createStore(todoReducer)

Ara Yazılım (Middleware)

Ara yazılım, bir eylemin gönderilmesi ile azaltıcıya ulaşması arasında üçüncü taraf bir uzantı noktası sağlar. Genellikle loglama, hata raporlama, asenkron görevleri gerçekleştirme vb. için kullanılır.

Örnek (Logger Middleware):

const logger = store => next => action => {
  console.log('gönderiliyor', action)
  let result = next(action)
  console.log('sonraki durum', store.getState())
  return result
}

const store = createStore(
  todoReducer,
  applyMiddleware(logger)
)

React-Redux

React-Redux, Redux için resmi React bağlayıcısıdır. React bileşenlerinizin Redux deposundan veri okumasına ve veriyi güncellemek için depoya eylem göndermesine olanak tanır.

Temel kavramlar:

  • Provider: Redux deposunu uygulamanızın geri kalanına sunar
  • connect(): Bir React bileşenini Redux deposuna bağlar
  • useSelector(): Redux deposu durumundan veri çıkarmak için kullanılan hook
  • useDispatch(): Eylem göndermek için kullanılan hook

Örnek:

import { Provider } from 'react-redux'
import store from './store'

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

// Bir bileşen dosyasında
import { useSelector, useDispatch } from 'react-redux'

function TodoList() {
  const todos = useSelector(state => state.todos)
  const dispatch = useDispatch()

  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  )
}

Redux Toolkit

Redux Toolkit, verimli Redux geliştirme için resmi, görüş sahibi, pil dahil araç setidir. Depo kurulumu, azaltıcı oluşturma, değişmez güncelleme mantığı ve daha fazlası gibi yaygın kullanım durumlarını basitleştiren yardımcı programlar içerir.

Temel özellikler:

  • configureStore(): Basitleştirilmiş depo kurulumu
  • createSlice(): Azaltıcı mantığını ve eylemleri birleştirir
  • createAsyncThunk: Basitleştirilmiş asenkron mantık

Örnek:

import { configureStore, createSlice } from '@reduxjs/toolkit'

const todosSlice = createSlice({
  name: 'todos',
  initialState: [],
  reducers: {
    addTodo: (state, action) => {
      state.push(action.payload)
    }
  }
})

export const { addTodo } = todosSlice.actions

const store = configureStore({
  reducer: {
    todos: todosSlice.reducer
  }
})

Redux Toolkit Alışveriş Sepeti Örneği

Bu örnek, basit bir alışveriş sepeti uygulamasının durumunu yönetmek için Redux Toolkit'in nasıl kullanılacağını göstermektedir.

Kurulum

Öncelikle, projemizi kuralım ve gerekli bağımlılıkları yükleyelim:

npm init -y
npm install @reduxjs/toolkit react-redux react react-dom

Dosya Yapısı

Aşağıdaki dosya yapısını oluşturun:

src/
  features/
    cart/
      cartSlice.js
    products/
      productsSlice.js
  app/
    store.js
  App.js
  index.js

Store'u Uygulamak

Redux Toolkit'in configureStore fonksiyonunu kullanarak store'umuzu oluşturmakla başlayalım.

Dosya: src/app/store.js

import { configureStore } from '@reduxjs/toolkit'
import cartReducer from '../features/cart/cartSlice'
import productsReducer from '../features/products/productsSlice'

export const store = configureStore({
  reducer: {
    cart: cartReducer,
    products: productsReducer,
  },
})

Slice'ları Oluşturma

Şimdi, sepet ve ürünlerin durumunu yönetmek için slice'larımızı oluşturalım.

Dosya: src/features/cart/cartSlice.js

import { createSlice } from '@reduxjs/toolkit'

const cartSlice = createSlice({
  name: 'cart',
  initialState: [],
  reducers: {
    addToCart: (state, action) => {
      const itemExists = state.find((item) => item.id === action.payload.id)
      if (itemExists) {
        itemExists.quantity++
      } else {
        state.push({ ...action.payload, quantity: 1 })
      }
    },
    incrementQuantity: (state, action) => {
      const item = state.find((item) => item.id === action.payload)
      item.quantity++
    },
    decrementQuantity: (state, action) => {
      const item = state.find((item) => item.id === action.payload)
      if (item.quantity === 1) {
        const index = state.findIndex((item) => item.id === action.payload)
        state.splice(index, 1)
      } else {
        item.quantity--
      }
    },
    removeFromCart: (state, action) => {
      const index = state.findIndex((item) => item.id === action.payload)
      state.splice(index, 1)
    },
  },
})

export const { addToCart, incrementQuantity, decrementQuantity, removeFromCart } = cartSlice.actions
export default cartSlice.reducer

Dosya: src/features/products/productsSlice.js

import { createSlice } from '@reduxjs/toolkit'

const productsSlice = createSlice({
  name: 'products',
  initialState: [
    { id: 1, name: 'iPhone 12', price: 999 },
    { id: 2, name: 'AirPods Pro', price: 249 },
    { id: 3, name: 'MacBook Air', price: 999 },
    { id: 4, name: 'iPad Pro', price: 799 },
  ],
  reducers: {},
})

export default productsSlice.reducer

React Bileşenlerini Oluşturma

Şimdi ürünleri ve sepeti görüntülemek için React bileşenlerimizi oluşturalım.

Dosya: src/App.js

import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { addToCart, incrementQuantity, decrementQuantity, removeFromCart } from './features/cart/cartSlice'

function App() {
  const dispatch = useDispatch()
  const products = useSelector((state) => state.products)
  const cart = useSelector((state) => state.cart)

  return (
    <div>
      <h1>Alışveriş Sepeti Örneği</h1>
      <h2>Ürünler</h2>
      {products.map((product) => (
        <div key={product.id}>
          <h3>{product.name}</h3>
          <p>${product.price}</p>
          <button onClick={() => dispatch(addToCart(product))}>Sepete Ekle</button>
        </div>
      ))}
      <h2>Sepet</h2>
      {cart.map((item) => (
        <div key={item.id}>
          <h3>{item.name}</h3>
          <p>
            ${item.price} x {item.quantity} = ${item.price * item.quantity}
          </p>
          <button onClick={() => dispatch(incrementQuantity(item.id))}>+</button>
          <button onClick={() => dispatch(decrementQuantity(item.id))}>-</button>
          <button onClick={() => dispatch(removeFromCart(item.id))}>Kaldır</button>
        </div>
      ))}
      <h3>Toplam: ${cart.reduce((total, item) => total + item.price * item.quantity, 0)}</h3>
    </div>
  )
}

export default App

Giriş Noktasını Ayarlama

Son olarak, uygulamayı Redux ile oluşturmak için giriş noktamızı ayarlayalım.

Dosya: src/index.js

import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import { store } from './app/store'
import App from './App'

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

Açıklama

Bu örnek, Redux Toolkit'in birkaç önemli özelliğini göstermektedir:

  1. configureStore: Redux store'umuzu minimal konfigürasyonla kurmak için bunu kullanıyoruz. Otomatik olarak Redux DevTools uzantısını kurar ve redux-thunk gibi bazı middleware'leri ekler.

  2. createSlice: Bu güçlü fonksiyon, otomatik olarak action creator'lar ve action type'lar oluşturur. Sepet ve ürün slice'larımızı tanımlamak için bunu kullanıyoruz.

  3. Immer Entegrasyonu: Redux Toolkit dahili olarak Immer kullanır, bu da bize reducer'larımızda aslında değişmez (immutable) güncellemelerle sonuçlanan "değiştirme" mantığı yazmamıza olanak tanır.

  4. React-Redux Hooks: State'e erişmek için useSelector ve action'ları dispatch etmek için useDispatch kullanıyoruz.

Bu alışveriş sepeti örneği şunları nasıl yapacağımızı gösterir:

  • Ürün listesini görüntüleme
  • Sepete ürün ekleme
  • Sepetteki ürünlerin miktarını artırma ve azaltma
  • Sepetten ürün çıkarma
  • Toplam fiyatı hesaplama

En İyi Uygulamalar

  1. Yeni projeler için Redux Toolkit kullanın
  2. Durumunuzu normalize edin
  3. Depodan okuma yapmak için seçici fonksiyonlar kullanın
  4. Eylem oluşturmak için eylem oluşturucular kullanın
  5. Anlamlı eylem türleri kullanın
  6. Azaltıcıları saf ve basit tutun
  7. Azaltıcılarda değişmez güncelleme kalıpları kullanın
  8. Yan etkiler için ara yazılım kullanın
  9. Hata ayıklama için Redux DevTools kullanın

İleri Düzey Kavramlar

Seçici Hafızalaması

Daha iyi performans için hafızalanmış seçici fonksiyonlar oluşturmak üzere reselect kullanın.

import { createSelector } from 'reselect'

const getTodos = state => state.todos
const getFilter = state => state.filter

const getVisibleTodos = createSelector(
  [getTodos, getFilter],
  (todos, filter) => {
    switch (filter) {
      case 'SHOW_COMPLETED':
        return todos.filter(t => t.completed)
      case 'SHOW_ACTIVE':
        return todos.filter(t => !t.completed)
      default:
        return todos
    }
  }
)

Redux Thunk ile Asenkron Eylemler

Redux Thunk ara yazılımı, bir eylem yerine bir fonksiyon döndüren eylem oluşturucular yazmanıza olanak tanır.

import { createAsyncThunk } from '@reduxjs/toolkit'

export const fetchTodos = createAsyncThunk(
  'todos/fetchTodos',
  async () => {
    const response = await fetch('/api/todos')
    return response.json()
  }
)

// Slice'ınızda
extraReducers: (builder) => {
  builder
    .addCase(fetchTodos.pending, (state) => {
      state.status = 'loading'
    })
    .addCase(fetchTodos.fulfilled, (state, action) => {
      state.status = 'succeeded'
      state.todos = action.payload
    })
    .addCase(fetchTodos.rejected, (state, action) => {
      state.status = 'failed'
      state.error = action.error.message
    })
}

Redux Thunk ile Asenkron İşlemler: Kullanıcı Kimlik Doğrulama Senaryosu

Bu örnek, kullanıcı kimlik doğrulama senaryosunda asenkron işlemleri yönetmek için Redux Thunk kullanmanın nasıl yapılacağını gösterir.

Senaryo

Bir kullanıcı kimlik doğrulama gerektiren bir web uygulamamız var. Kullanıcı girişi için asenkron API çağrısını yönetmek için Redux Thunk kullanarak bir giriş özelliği uygulayacağız.

Uygulama

1. Asenkron Thunk Oluşturma

İlk olarak, giriş işlemi için bir asenkron thunk oluşturacağız:

import { createAsyncThunk } from '@reduxjs/toolkit'

export const loginUser = createAsyncThunk(
  'auth/loginUser',
  async (credentials, { rejectWithValue }) => {
    try {
      const response = await fetch('/api/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(credentials)
      })
      
      if (!response.ok) {
        throw new Error('Giriş başarısız')
      }
      
      const data = await response.json()
      return data
    } catch (error) {
      return rejectWithValue(error.message)
    }
  }
)

2. Auth Slice'ı Kurma

Sonraki adım, kimlik doğrulama durumunu yönetmek için bir auth slice oluşturmaktır:

import { createSlice } from '@reduxjs/toolkit'
import { loginUser } from './authThunks'

const authSlice = createSlice({
  name: 'auth',
  initialState: {
    user: null,
    status: 'idle',
    error: null
  },
  reducers: {
    logout: (state) => {
      state.user = null
      state.status = 'idle'
      state.error = null
    }
  },
  extraReducers: (builder) => {
    builder
      .addCase(loginUser.pending, (state) => {
        state.status = 'loading'
      })
      .addCase(loginUser.fulfilled, (state, action) => {
        state.status = 'succeeded'
        state.user = action.payload
        state.error = null
      })
      .addCase(loginUser.rejected, (state, action) => {
        state.status = 'failed'
        state.error = action.payload
      })
  }
})

export const { logout } = authSlice.actions
export default authSlice.reducer

3. Bir Bileşende Kullanma

Son olarak, loginUser thunk'ını bir React bileşeninde kullanabiliriz:

import React, { useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { loginUser } from './authThunks'

const LoginForm = () => {
  const [username, setUsername] = useState('')
  const [password, setPassword] = useState('')
  const dispatch = useDispatch()
  const { status, error } = useSelector(state => state.auth)

  const handleSubmit = (e) => {
    e.preventDefault()
    dispatch(loginUser({ username, password }))
  }

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="text"
        value={username}
        onChange={(e) => setUsername(e.target.value)}
        placeholder="Kullanıcı Adı"
      />
      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Şifre"
      />
      <button type="submit" disabled={status === 'loading'}>
        {status === 'loading' ? 'Giriş Yapılıyor...' : 'Giriş Yap'}
      </button>
      {error && <p>{error}</p>}
    </form>
  )
}

export default LoginForm

Açıklama

loginUser adında bir asenkron thunk oluşturuyoruz, bu thunk API çağrısını kullanıcı girişi için yönetir. auth slice'ı, giriş işleminin bekleyen, başarılı ve başarısız durumlarını içeren kimlik doğrulama durumunu yönetir. React bileşeninde, form gönderildiğinde loginUser thunk'ını dispatch ediyoruz. Bileşen ayrıca yüklenme durumunu ve herhangi bir hata mesajını görüntüler.

Normalize Edilmiş Durum Şekli

Karmaşık uygulamalar için, durum şeklinizi normalize etmeyi düşünün:

{
  entities: {
    todos: {
      byId: {
        1: { id: 1, text: 'Süt al', completed: false },
        2: { id: 2, text: 'Köpeği gezdirn', completed: true }
      },
      allIds: [1, 2]
    },
    users: {
      byId: {
        1: { id: 1, name: 'Ahmet Yılmaz' },
        2: { id: 2, name: 'Ayşe Demir' }
      },
      allIds: [1, 2]
    }
  }
}

Redux Testi

  1. Eylem oluşturucuları test edin
  2. Azaltıcıları test edin
  3. Seçicileri test edin
  4. Birim testi için Jest kullanın
  5. Entegrasyon testi için Redux Mock Store kullanın

Örnek (Bir azaltıcıyı test etme):

import todoReducer from './todoReducer'
import { addTodo } from './todoActions'

describe('todo azaltıcı', () => {
  it('ADD_TODO'yu işleyebilmeli', () => {
    const initialState = []
    const action = addTodo('Redux Öğren')
    const nextState = todoReducer(initialState, action)

    expect(nextState).toEqual([
      { text: 'Redux Öğren', completed: false }
    ])
  })
})

2024 © Tüm hakları saklıdır - buraxta.com