Kurs React Native · Moduł 4: Stan i dane z API

useState i useReducer - Skarbiec Stanu

3 min czytania ·
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. 1. Jak poprawnie zainicjalizować useState z wartością początkową?

  2. 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.

Przydatne artykuły