Kurs React Native · Moduł 4: Stan i dane z API
Context API - Globalne Skarby
W tej lekcji4
Context API pozwala dzielić dane między komponentami bez "prop drilling" - przekazywania propsów przez wiele poziomów.
Tworzenie Context
// contexts/TreasureContext.tsx
import { createContext, useContext, useState, useReducer, ReactNode } from 'react';
// Typy
interface Treasure {
id: string;
name: string;
value: number;
rarity: 'common' | 'rare' | 'legendary';
}
interface TreasureContextType {
treasures: Treasure[];
totalValue: number;
addTreasure: (treasure: Omit<Treasure, 'id'>) => void;
removeTreasure: (id: string) => void;
clearAll: () => void;
}
// Context z wartością domyślną
const TreasureContext = createContext<TreasureContextType | undefined>(undefined);
// Provider
export function TreasureProvider({ children }: { children: ReactNode }) {
const [treasures, setTreasures] = useState<Treasure[]>([]);
const totalValue = treasures.reduce((sum, t) => sum + t.value, 0);
const addTreasure = (treasure: Omit<Treasure, 'id'>) => {
const newTreasure: Treasure = {
...treasure,
id: Date.now().toString(),
};
setTreasures(prev => [...prev, newTreasure]);
};
const removeTreasure = (id: string) => {
setTreasures(prev => prev.filter(t => t.id !== id));
};
const clearAll = () => {
setTreasures([]);
};
return (
<TreasureContext.Provider
value={{ treasures, totalValue, addTreasure, removeTreasure, clearAll }}
>
{children}
</TreasureContext.Provider>
);
}
// Custom hook
export function useTreasure() {
const context = useContext(TreasureContext);
if (!context) {
throw new Error('useTreasure must be used within TreasureProvider');
}
return context;
}Użycie w aplikacji
// App.tsx
import { TreasureProvider } from './contexts/TreasureContext';
export default function App() {
return (
<TreasureProvider>
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Vault" component={VaultScreen} />
</Stack.Navigator>
</NavigationContainer>
</TreasureProvider>
);
}
// Dowolny komponent w drzewie
function TreasureList() {
const { treasures, totalValue, removeTreasure } = useTreasure();
return (
<View>
<Text>Wartość skarbca: {totalValue} złota</Text>
<FlatList
data={treasures}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<Pressable onPress={() => removeTreasure(item.id)}>
<Text>{item.name} - {item.value} złota</Text>
</Pressable>
)}
/>
</View>
);
}
// Inny komponent
function AddTreasureForm() {
const { addTreasure } = useTreasure();
const [name, setName] = useState('');
const handleAdd = () => {
addTreasure({ name, value: 100, rarity: 'common' });
setName('');
};
return (
<View>
<TextInput value={name} onChangeText={setName} placeholder="Nazwa skarbu" />
<Pressable onPress={handleAdd}>
<Text>Dodaj skarb</Text>
</Pressable>
</View>
);
}Optymalizacja z useMemo i useCallback
import { useMemo, useCallback } from 'react';
export function TreasureProvider({ children }: { children: ReactNode }) {
const [treasures, setTreasures] = useState<Treasure[]>([]);
// Memoizacja wartości obliczanych
const totalValue = useMemo(() => {
return treasures.reduce((sum, t) => sum + t.value, 0);
}, [treasures]);
const legendaryCount = useMemo(() => {
return treasures.filter(t => t.rarity === 'legendary').length;
}, [treasures]);
// Memoizacja funkcji
const addTreasure = useCallback((treasure: Omit<Treasure, 'id'>) => {
setTreasures(prev => [...prev, { ...treasure, id: Date.now().toString() }]);
}, []);
const removeTreasure = useCallback((id: string) => {
setTreasures(prev => prev.filter(t => t.id !== id));
}, []);
// Memoizacja wartości kontekstu
const value = useMemo(() => ({
treasures,
totalValue,
legendaryCount,
addTreasure,
removeTreasure,
}), [treasures, totalValue, legendaryCount, addTreasure, removeTreasure]);
return (
<TreasureContext.Provider value={value}>
{children}
</TreasureContext.Provider>
);
}Wiele Context'ów
// Rozdzielenie odpowiedzialności
<AuthProvider>
<ThemeProvider>
<TreasureProvider>
<NotificationProvider>
<App />
</NotificationProvider>
</TreasureProvider>
</ThemeProvider>
</AuthProvider>Context API świetnie sprawdza się dla stanu globalnego jak autoryzacja, motyw czy ustawienia.
Kod do tej lekcji: App.tsx
import React, { createContext, useContext, useState, ReactNode } from 'react';
import { View, Text, Pressable, FlatList, StyleSheet } from 'react-native';
interface Treasure {
id: string;
name: string;
}
interface TreasureContextType {
treasures: Treasure[];
addTreasure: (name: string) => void;
}
// TODO: stwórz Context z wartością domyślną undefined
const TreasureContext = createContext<TreasureContextType | undefined>(undefined);
function TreasureProvider({ children }: { children: ReactNode }) {
const [treasures, setTreasures] = useState<Treasure[]>([]);
const addTreasure = (name: string) => {
// TODO: dodaj nowy skarb do tablicy z unikalnym id (Date.now().toString())
// setTreasures(prev => [...prev, { id: Date.now().toString(), name }]);
};
return (
// TODO: udostępnij wartość { treasures, addTreasure } przez Provider
<TreasureContext.Provider value={{ treasures, addTreasure }}>
{children}
</TreasureContext.Provider>
);
}
// TODO: stwórz custom hook useTreasure, który czyta kontekst
// i rzuca błąd, gdy jest używany poza Providerem
function useTreasure() {
const context = useContext(TreasureContext);
if (!context) {
throw new Error('useTreasure must be used within TreasureProvider');
}
return context;
}
function TreasureScreen() {
const { treasures, addTreasure } = useTreasure();
return (
<View style={styles.container}>
<Pressable style={styles.button} onPress={() => addTreasure('Perła')}>
<Text style={styles.buttonText}>Dodaj perłę</Text>
</Pressable>
<FlatList
data={treasures}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <Text style={styles.item}>{item.name}</Text>}
/>
</View>
);
}
export default function App() {
return (
<TreasureProvider>
<TreasureScreen />
</TreasureProvider>
);
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 20, backgroundColor: '#1a5276' },
button: {
paddingVertical: 14,
borderRadius: 8,
backgroundColor: '#2980b9',
alignItems: 'center',
marginBottom: 16,
},
buttonText: { color: '#fff', fontSize: 16, fontWeight: '600' },
item: { color: '#aed6f1', fontSize: 18, paddingVertical: 6 },
});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 utworzyć nowy Context w React?
2. Co robi Context.Provider?
Zadania praktyczne w grze
- Układanie w poziomie
Ułóż poprawne użycie hooka useContext:
- Edytor kodu
Uzupełnij kod TreasureProvider i custom hooka useTreasure.