Ders İçeriği

Durum Yönetimi Nedir?

Durum yönetimi (State Management), uygulamanızın verilerinin nasıl saklandığı, güncellendiği ve bileşenler arasında nasıl paylaşıldığını kontrol eden sistemdir. React Native uygulamalarında etkili durum yönetimi, kullanıcı deneyimini ve performansı doğrudan etkiler.

Durum Yönetiminin Önemi:
  • Veri tutarlılığını sağlar
  • Bileşenler arası iletişimi kolaylaştırır
  • Uygulamanın öngörülebilirliğini artırır
  • Debugging ve test etmeyi kolaylaştırır
  • Performansı optimize eder

useState Hook'u

useState, React'in en temel durum yönetimi hook'udur. Fonksiyonel bileşenlerde yerel durum oluşturmak için kullanılır.

import React, { useState } from 'react'; import { View, Text, Button, StyleSheet } from 'react-native'; const CounterComponent = () => { // useState hook'u ile sayaç durumu oluşturma const [count, setCount] = useState(0); const [name, setName] = useState('Kullanıcı'); const [isVisible, setIsVisible] = useState(true); const increment = () => { setCount(count + 1); // veya fonksiyonel güncelleme: // setCount(prevCount => prevCount + 1); }; const decrement = () => { setCount(prevCount => prevCount - 1); }; const reset = () => { setCount(0); }; return ( <View style={styles.container}> <Text style={styles.title}>Sayaç Uygulaması</Text> {isVisible && ( <Text style={styles.counter}>Sayı: {count}</Text> )} <Text style={styles.greeting}>Merhaba, {name}!</Text> <View style={styles.buttonContainer}> <Button title="Artır" onPress={increment} /> <Button title="Azalt" onPress={decrement} /> <Button title="Sıfırla" onPress={reset} /> <Button title={isVisible ? "Gizle" : "Göster"} onPress={() => setIsVisible(!isVisible)} /> </View> </View> ); }; const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20, }, title: { fontSize: 24, fontWeight: 'bold', marginBottom: 20, }, counter: { fontSize: 48, fontWeight: 'bold', color: '#3498db', marginBottom: 20, }, greeting: { fontSize: 18, marginBottom: 30, color: '#2c3e50', }, buttonContainer: { flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'center', gap: 10, }, }); export default CounterComponent;

useState Simülasyonu

0
  

Butonlara tıklayarak useState'in nasıl çalıştığını görün

useEffect Hook'u

useEffect, bileşenin yaşam döngüsü olaylarını yönetmek için kullanılır. API çağrıları, zamanlayıcılar ve temizlik işlemleri için idealdir.

import React, { useState, useEffect } from 'react'; import { View, Text, StyleSheet } from 'react-native'; const TimerComponent = () => { const [seconds, setSeconds] = useState(0); const [isActive, setIsActive] = useState(true); // Bileşen mount olduğunda çalışır useEffect(() => { console.log('Bileşen mount oldu'); // Cleanup fonksiyonu - bileşen unmount olduğunda çalışır return () => { console.log('Bileşen unmount oldu'); }; }, []); // Boş dependency array - sadece mount/unmount'ta çalışır // seconds değiştiğinde çalışır useEffect(() => { console.log('Saniye değişti:', seconds); }, [seconds]); // seconds dependency'si // Timer effect'i useEffect(() => { let interval = null; if (isActive) { interval = setInterval(() => { setSeconds(prevSeconds => prevSeconds + 1); }, 1000); } else if (!isActive && seconds !== 0) { clearInterval(interval); } // Cleanup fonksiyonu return () => clearInterval(interval); }, [isActive, seconds]); // API çağrısı örneği useEffect(() => { const fetchData = async () => { try { const response = await fetch('https://api.example.com/data'); const data = await response.json(); console.log('Veri alındı:', data); } catch (error) { console.error('Hata:', error); } }; fetchData(); }, []); // Sadece mount'ta çalışır const formatTime = (totalSeconds) => { const minutes = Math.floor(totalSeconds / 60); const secs = totalSeconds % 60; return `${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; }; return ( <View style={styles.container}> <Text style={styles.title}>Zamanlayıcı</Text> <Text style={styles.timer}>{formatTime(seconds)}</Text> <Text style={styles.status}> Durum: {isActive ? 'Çalışıyor' : 'Durduruldu'} </Text> </View> ); }; const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20, }, title: { fontSize: 24, fontWeight: 'bold', marginBottom: 20, }, timer: { fontSize: 48, fontWeight: 'bold', color: '#e74c3c', marginBottom: 10, }, status: { fontSize: 16, color: '#7f8c8d', }, }); export default TimerComponent;

🔄 useEffect Dependency Patterns

Dependency ArrayÇalışma ZamanıKullanım Alanı
useEffect(() => {}, [])Sadece mount/unmountAPI çağrıları, event listener'lar
useEffect(() => {}, [value])value değiştiğindeBelirli state değişikliklerini izleme
useEffect(() => {})Her render'daPerformans sorunlarına neden olabilir

Context API ile Global State

Context API, prop drilling problemini çözmek ve global state yönetimi için React'in yerleşik çözümüdür. Küçük ve orta ölçekli uygulamalar için idealdir.

import React, { createContext, useContext, useReducer } from 'react'; import { View, Text, Button, StyleSheet } from 'react-native'; // 1. Context oluşturma const AppContext = createContext(); // 2. Initial state const initialState = { user: null, theme: 'light', notifications: [], isLoading: false, }; // 3. Reducer fonksiyonu const appReducer = (state, action) => { switch (action.type) { case 'SET_USER': return { ...state, user: action.payload }; case 'SET_THEME': return { ...state, theme: action.payload }; case 'ADD_NOTIFICATION': return { ...state, notifications: [...state.notifications, action.payload] }; case 'SET_LOADING': return { ...state, isLoading: action.payload }; case 'CLEAR_NOTIFICATIONS': return { ...state, notifications: [] }; default: return state; } }; // 4. Context Provider bileşeni export const AppProvider = ({ children }) => { const [state, dispatch] = useReducer(appReducer, initialState); // Action creators const setUser = (user) => { dispatch({ type: 'SET_USER', payload: user }); }; const setTheme = (theme) => { dispatch({ type: 'SET_THEME', payload: theme }); }; const addNotification = (notification) => { dispatch({ type: 'ADD_NOTIFICATION', payload: notification }); }; const setLoading = (isLoading) => { dispatch({ type: 'SET_LOADING', payload: isLoading }); }; const clearNotifications = () => { dispatch({ type: 'CLEAR_NOTIFICATIONS' }); }; const value = { ...state, setUser, setTheme, addNotification, setLoading, clearNotifications, }; return ( <AppContext.Provider value={value}> {children} </AppContext.Provider> ); }; // 5. Custom hook export const useApp = () => { const context = useContext(AppContext); if (!context) { throw new Error('useApp must be used within AppProvider'); } return context; }; // 6. Bileşenlerde kullanım const UserProfile = () => { const { user, setUser, theme, setTheme } = useApp(); const handleLogin = () => { setUser({ id: 1, name: 'Ahmet Yılmaz', email: 'ahmet@example.com' }); }; const toggleTheme = () => { setTheme(theme === 'light' ? 'dark' : 'light'); }; return ( <View style={[ styles.container, { backgroundColor: theme === 'light' ? '#fff' : '#2c3e50' } ]}> <Text style={[ styles.title, { color: theme === 'light' ? '#2c3e50' : '#fff' } ]}> Kullanıcı Profili </Text> {user ? ( <View> <Text style={styles.userInfo}>İsim: {user.name}</Text> <Text style={styles.userInfo}>Email: {user.email}</Text> </View> ) : ( <Text style={styles.noUser}>Giriş yapılmamış</Text> )} <Button title="Giriş Yap" onPress={handleLogin} /> <Button title="Tema Değiştir" onPress={toggleTheme} /> </View> ); }; // 7. Ana uygulama const App = () => { return ( <AppProvider> <UserProfile /> </AppProvider> ); }; const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20, }, title: { fontSize: 24, fontWeight: 'bold', marginBottom: 20, }, userInfo: { fontSize: 16, marginBottom: 10, color: '#34495e', }, noUser: { fontSize: 16, color: '#7f8c8d', marginBottom: 20, }, }); export default App;

Pratik Örnek: Todo Uygulaması

Context API kullanarak basit bir Todo uygulaması oluşturalım:

Todo Uygulaması Simülasyonu

 

Toplam: 0 görev

import React, { createContext, useContext, useReducer } from 'react'; import { View, Text, TextInput, Button, FlatList, StyleSheet } from 'react-native'; // Todo Context const TodoContext = createContext(); const todoReducer = (state, action) => { switch (action.type) { case 'ADD_TODO': return { ...state, todos: [...state.todos, { id: Date.now(), text: action.payload, completed: false, }] }; case 'TOGGLE_TODO': return { ...state, todos: state.todos.map(todo => todo.id === action.payload ? { ...todo, completed: !todo.completed } : todo ) }; case 'DELETE_TODO': return { ...state, todos: state.todos.filter(todo => todo.id !== action.payload) }; case 'SET_FILTER': return { ...state, filter: action.payload }; default: return state; } }; export const TodoProvider = ({ children }) => { const [state, dispatch] = useReducer(todoReducer, { todos: [], filter: 'all' // all, active, completed }); const addTodo = (text) => { if (text.trim()) { dispatch({ type: 'ADD_TODO', payload: text.trim() }); } }; const toggleTodo = (id) => { dispatch({ type: 'TOGGLE_TODO', payload: id }); }; const deleteTodo = (id) => { dispatch({ type: 'DELETE_TODO', payload: id }); }; const setFilter = (filter) => { dispatch({ type: 'SET_FILTER', payload: filter }); }; const getFilteredTodos = () => { switch (state.filter) { case 'active': return state.todos.filter(todo => !todo.completed); case 'completed': return state.todos.filter(todo => todo.completed); default: return state.todos; } }; const value = { todos: getFilteredTodos(), allTodos: state.todos, filter: state.filter, addTodo, toggleTodo, deleteTodo, setFilter, }; return ( <TodoContext.Provider value={value}> {children} </TodoContext.Provider> ); }; export const useTodos = () => { const context = useContext(TodoContext); if (!context) { throw new Error('useTodos must be used within TodoProvider'); } return context; }; // Todo bileşenleri const TodoInput = () => { const [text, setText] = useState(''); const { addTodo } = useTodos(); const handleSubmit = () => { addTodo(text); setText(''); }; return ( <View style={styles.inputContainer}> <TextInput style={styles.input} value={text} onChangeText={setText} placeholder="Yeni görev ekleyin..." onSubmitEditing={handleSubmit} /> <Button title="Ekle" onPress={handleSubmit} /> </View> ); }; const TodoItem = ({ todo }) => { const { toggleTodo, deleteTodo } = useTodos(); return ( <View style={styles.todoItem}> <Text style={[ styles.todoText, todo.completed && styles.completedText ]} onPress={() => toggleTodo(todo.id)} > {todo.text} </Text> <Button title="Sil" onPress={() => deleteTodo(todo.id)} color="#e74c3c" /> </View> ); }; const TodoApp = () => { const { todos, allTodos, filter, setFilter } = useTodos(); const completedCount = allTodos.filter(todo => todo.completed).length; const activeCount = allTodos.filter(todo => !todo.completed).length; return ( <View style={styles.container}> <Text style={styles.title}>Todo Uygulaması</Text> <TodoInput /> <View style={styles.filterContainer}> <Button title="Tümü" onPress={() => setFilter('all')} color={filter === 'all' ? '#3498db' : '#95a5a6'} /> <Button title="Aktif" onPress={() => setFilter('active')} color={filter === 'active' ? '#3498db' : '#95a5a6'} /> <Button title="Tamamlanan" onPress={() => setFilter('completed')} color={filter === 'completed' ? '#3498db' : '#95a5a6'} /> </View> <FlatList data={todos} keyExtractor={(item) => item.id.toString()} renderItem={({ item }) => <TodoItem todo={item} />} style={styles.list} /> <Text style={styles.stats}> Aktif: {activeCount}, Tamamlanan: {completedCount} </Text> </View> ); }; const App = () => { return ( <TodoProvider> <TodoApp /> </TodoProvider> ); }; const styles = StyleSheet.create({ container: { flex: 1, padding: 20, backgroundColor: '#f8f9fa', }, title: { fontSize: 24, fontWeight: 'bold', textAlign: 'center', marginBottom: 20, color: '#2c3e50', }, inputContainer: { flexDirection: 'row', marginBottom: 20, gap: 10, }, input: { flex: 1, borderWidth: 1, borderColor: '#ddd', padding: 10, borderRadius: 5, backgroundColor: 'white', }, filterContainer: { flexDirection: 'row', justifyContent: 'space-around', marginBottom: 20, }, list: { flex: 1, }, todoItem: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', padding: 15, backgroundColor: 'white', marginBottom: 5, borderRadius: 5, }, todoText: { flex: 1, fontSize: 16, color: '#2c3e50', }, completedText: { textDecorationLine: 'line-through', color: '#7f8c8d', }, stats: { textAlign: 'center', marginTop: 20, fontSize: 16, color: '#7f8c8d', }, }); export default App;

Redux ile Gelişmiş State Yönetimi

Büyük ve karmaşık uygulamalar için Redux, daha güçlü ve öngörülebilir state yönetimi sunar. Redux Toolkit kullanarak modern Redux geliştirme yapabilirsiniz.

Redux Ne Zaman Kullanılmalı?
  • Çok sayıda bileşen aynı state'i paylaşıyorsa
  • State güncellemeleri karmaşık iş mantığı gerektiriyorsa
  • State değişikliklerini izlemeniz gerekiyorsa
  • Time-travel debugging özelliği istiyorsanız
// Redux Toolkit kurulumu npm install @reduxjs/toolkit react-redux // store/counterSlice.js import { createSlice } from '@reduxjs/toolkit'; const counterSlice = createSlice({ name: 'counter', initialState: { value: 0, history: [], }, reducers: { increment: (state) => { state.value += 1; state.history.push(`Artırıldı: ${state.value}`); }, decrement: (state) => { state.value -= 1; state.history.push(`Azaltıldı: ${state.value}`); }, incrementByAmount: (state, action) => { state.value += action.payload; state.history.push(`${action.payload} eklendi: ${state.value}`); }, reset: (state) => { state.value = 0; state.history.push('Sıfırlandı'); }, }, }); export const { increment, decrement, incrementByAmount, reset } = counterSlice.actions; export default counterSlice.reducer; // store/index.js import { configureStore } from '@reduxjs/toolkit'; import counterReducer from './counterSlice'; export const store = configureStore({ reducer: { counter: counterReducer, }, }); // App.js import React from 'react'; import { Provider } from 'react-redux'; import { store } from './store'; import CounterApp from './CounterApp'; const App = () => { return ( <Provider store={store}> <CounterApp /> </Provider> ); }; export default App; // CounterApp.js import React from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { View, Text, Button, StyleSheet } from 'react-native'; import { increment, decrement, incrementByAmount, reset } from './store/counterSlice'; const CounterApp = () => { const count = useSelector((state) => state.counter.value); const history = useSelector((state) => state.counter.history); const dispatch = useDispatch(); return ( <View style={styles.container}> <Text style={styles.title}>Redux Counter</Text> <Text style={styles.counter}>{count}</Text> <View style={styles.buttonContainer}> <Button title="+" onPress={() => dispatch(increment())} /> <Button title="-" onPress={() => dispatch(decrement())} /> <Button title="+5" onPress={() => dispatch(incrementByAmount(5))} /> <Button title="Reset" onPress={() => dispatch(reset())} /> </View> <Text style={styles.historyTitle}>Geçmiş:</Text> {history.slice(-5).map((item, index) => ( <Text key={index} style={styles.historyItem}>{item}</Text> ))} </View> ); }; const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20, }, title: { fontSize: 24, fontWeight: 'bold', marginBottom: 20, }, counter: { fontSize: 48, fontWeight: 'bold', color: '#3498db', marginBottom: 30, }, buttonContainer: { flexDirection: 'row', gap: 10, marginBottom: 30, }, historyTitle: { fontSize: 18, fontWeight: 'bold', marginBottom: 10, }, historyItem: { fontSize: 14, color: '#7f8c8d', marginBottom: 5, }, }); export default CounterApp;

📊 State Yönetimi Karşılaştırması

YöntemKullanım AlanıAvantajlarDezavantajlar
useStateYerel bileşen state'iBasit, hızlıSadece yerel kullanım
Context APIOrta ölçekli uygulamalarYerleşik, prop drilling çözümüPerformans sorunları olabilir
ReduxBüyük, karmaşık uygulamalarÖngörülebilir, debug araçlarıKarmaşık kurulum, boilerplate
ZustandOrta-büyük uygulamalarBasit API, TypeScript desteğiDaha az topluluk desteği

Sonraki Adımlar

Bu derste React Native'de durum yönetimini öğrendiniz. Bir sonraki derste API entegrasyonu konusunu ele alacak ve uygulamanızı backend servislerle nasıl bağlayacağınızı öğreneceksiniz.

Bu Derste Öğrendikleriniz:
  • useState ile yerel state yönetimi
  • useEffect ile yaşam döngüsü yönetimi
  • Context API ile global state
  • useReducer ile karmaşık state mantığı
  • Redux ile gelişmiş state yönetimi
  • State yönetimi yöntemlerinin karşılaştırması