Kurs React Native · Moduł 4: Stan i dane z API
useState i useReducer - Skarbiec Stanu
W tej lekcji3
Witaj w skarbcu Atlantydy! Tutaj przechowujemy najcenniejsze dane naszej aplikacji. Zacznijmy od podstawowych hooków zarządzania stanem.
useState - Podstawowy skarbiec
import { useState } from 'react';
import { View, Text, Pressable, StyleSheet } from 'react-native';
function TreasureCounter() {
// Prosty stan liczbowy
const [treasures, setTreasures] = useState(0);
// Stan obiektowy
const [treasure, setTreasure] = useState({
gold: 0,
pearls: 0,
gems: 0,
});
// Stan tablicowy
const [collectedItems, setCollectedItems] = useState<string[]>([]);
// Aktualizacja pojedynczej wartości
const addTreasure = () => {
setTreasures(prev => prev + 1);
};
// Aktualizacja obiektu (niezmienność!)
const addGold = () => {
setTreasure(prev => ({
...prev,
gold: prev.gold + 10,
}));
};
// Dodawanie do tablicy
const collectItem = (item: string) => {
setCollectedItems(prev => [...prev, item]);
};
// Usuwanie z tablicy
const removeItem = (index: number) => {
setCollectedItems(prev => prev.filter((_, i) => i !== index));
};
return (
<View style={styles.container}>
<Text style={styles.title}>Skarbiec: {treasures} skarbów</Text>
<Pressable style={styles.button} onPress={addTreasure}>
<Text style={styles.buttonText}>Dodaj skarb</Text>
</Pressable>
<Text>Złoto: {treasure.gold}</Text>
<Pressable onPress={addGold}>
<Text>Zbierz złoto</Text>
</Pressable>
<Text>Przedmioty: {collectedItems.join(', ')}</Text>
<Pressable onPress={() => collectItem('Perła')}>
<Text>Zbierz perłę</Text>
</Pressable>
</View>
);
}Leniwa inicjalizacja stanu
// Dla kosztownych operacji inicjalizacyjnych
const [data, setData] = useState(() => {
// Ta funkcja wykonuje się tylko raz przy pierwszym renderze
return expensiveComputation();
});
// Przykład: ładowanie z AsyncStorage
const [settings, setSettings] = useState<Settings | null>(null);
useEffect(() => {
const loadSettings = async () => {
const saved = await AsyncStorage.getItem('settings');
if (saved) {
setSettings(JSON.parse(saved));
}
};
loadSettings();
}, []);useReducer - Dla złożonego stanu
import { useReducer } from 'react';
// Typy
interface TreasureState {
gold: number;
pearls: number;
gems: number;
items: string[];
isLoading: boolean;
error: string | null;
}
type TreasureAction =
| { type: 'ADD_GOLD'; amount: number }
| { type: 'ADD_PEARL' }
| { type: 'ADD_GEM'; count: number }
| { type: 'COLLECT_ITEM'; item: string }
| { type: 'SET_LOADING'; isLoading: boolean }
| { type: 'SET_ERROR'; error: string }
| { type: 'RESET' };
// Reducer
function treasureReducer(state: TreasureState, action: TreasureAction): TreasureState {
switch (action.type) {
case 'ADD_GOLD':
return { ...state, gold: state.gold + action.amount };
case 'ADD_PEARL':
return { ...state, pearls: state.pearls + 1 };
case 'ADD_GEM':
return { ...state, gems: state.gems + action.count };
case 'COLLECT_ITEM':
return { ...state, items: [...state.items, action.item] };
case 'SET_LOADING':
return { ...state, isLoading: action.isLoading };
case 'SET_ERROR':
return { ...state, error: action.error, isLoading: false };
case 'RESET':
return initialState;
default:
return state;
}
}
// Stan początkowy
const initialState: TreasureState = {
gold: 0,
pearls: 0,
gems: 0,
items: [],
isLoading: false,
error: null,
};
// Komponent
function TreasureVault() {
const [state, dispatch] = useReducer(treasureReducer, initialState);
return (
<View>
<Text>Złoto: {state.gold}</Text>
<Text>Perły: {state.pearls}</Text>
<Pressable onPress={() => dispatch({ type: 'ADD_GOLD', amount: 100 })}>
<Text>Dodaj 100 złota</Text>
</Pressable>
<Pressable onPress={() => dispatch({ type: 'ADD_PEARL' })}>
<Text>Dodaj perłę</Text>
</Pressable>
<Pressable onPress={() => dispatch({ type: 'RESET' })}>
<Text>Resetuj skarbiec</Text>
</Pressable>
</View>
);
}useState świetnie sprawdza się dla prostego stanu, useReducer dla złożonej logiki z wieloma akcjami.
Kod do tej lekcji: App.tsx
import React, { useState } from 'react';
import { View, Text, Pressable, StyleSheet } from 'react-native';
export default function App() {
// TODO: stwórz stan licznika skarbów z wartością początkową 0
// const [treasures, setTreasures] = useState(0);
const addTreasure = () => {
// TODO: zwiększ licznik o 1 używając funkcji aktualizującej
// setTreasures(prev => prev + 1);
};
const removeTreasure = () => {
// TODO: zmniejsz licznik o 1, ale nie pozwól zejść poniżej 0
// setTreasures(prev => (prev > 0 ? prev - 1 : 0));
};
return (
<View style={styles.container}>
{/* TODO: wyświetl aktualną liczbę skarbów zamiast 0 */}
<Text style={styles.title}>Skarbiec: 0 skarbów</Text>
<View style={styles.row}>
<Pressable style={styles.button} onPress={removeTreasure}>
<Text style={styles.buttonText}>-</Text>
</Pressable>
<Pressable style={styles.button} onPress={addTreasure}>
<Text style={styles.buttonText}>+</Text>
</Pressable>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#1a5276',
},
title: {
fontSize: 22,
color: '#fff',
marginBottom: 24,
},
row: {
flexDirection: 'row',
gap: 16,
},
button: {
paddingVertical: 14,
paddingHorizontal: 28,
borderRadius: 8,
backgroundColor: '#2980b9',
},
buttonText: {
color: '#fff',
fontSize: 20,
fontWeight: '600',
},
});Hasła z dokumentacji w tej lekcji
Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Jak poprawnie zainicjalizować useState z wartością początkową?
2. Jak poprawnie zaktualizować pojedyncze pole w obiekcie stanu?
To 2 z 3 pytań do tej lekcji. Pozostałe rozwiążesz w grze.
Zadania praktyczne w grze
- Układanie w poziomie
Ułóż poprawną aktualizację stanu z zachowaniem niezmienności:
- Klikanie w kolejności
Kliknij elementy tworzenia akcji dla useReducer:
- Edytor kodu
Uzupełnij kod licznika z funkcją dodawania i odejmowania.