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:
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, 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, eylemleri ve azaltıcıları bir araya getiren nesnedir. Aşağıdaki sorumlulukları vardır:
getState()
ile duruma erişim sağlardispatch(action)
ile durumun güncellenmesine izin verirsubscribe(listener)
ile dinleyicileri kaydederÖrnek:
import { createStore } from 'redux'
import todoReducer from './reducers'
const store = createStore(todoReducer)
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, 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 sunarconnect()
: Bir React bileşenini Redux deposuna bağlaruseSelector()
: Redux deposu durumundan veri çıkarmak için kullanılan hookuseDispatch()
: 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, 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 kurulumucreateSlice()
: Azaltıcı mantığını ve eylemleri birleştirircreateAsyncThunk
: 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
}
})
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.
Öncelikle, projemizi kuralım ve gerekli bağımlılıkları yükleyelim:
npm init -y
npm install @reduxjs/toolkit react-redux react react-dom
Aşağıdaki dosya yapısını oluşturun:
src/
features/
cart/
cartSlice.js
products/
productsSlice.js
app/
store.js
App.js
index.js
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,
},
})
Ş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
Ş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
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')
)
Bu örnek, Redux Toolkit'in birkaç önemli özelliğini göstermektedir:
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.
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.
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.
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:
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 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
})
}
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.
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.
İ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)
}
}
)
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
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
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.
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]
}
}
}
Ö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