Jede Schicht hat eine Aufgabe und kennt nur die Schicht darunter.
Der Screen ist immer in genau einem von drei Zuständen.
import type { City } from '@/model/city';
import type { CurrentWeather } from '@/model/current-weather';
export type WeatherUiState =
| { status: 'loading' }
| { status: 'success'; city: City; weather: CurrentWeather }
| { status: 'error'; message: string };Ein Custom Hook ist eine Funktion, deren Name mit use beginnt und die selbst Hooks aufruft. Er hält den Zustand und gibt ihn zurück.
import { useState } from 'react';
import { fakeCities, fakeCurrentWeather } from '@/data/fake-data';
import type { WeatherUiState } from '@/ui/weather-ui-state';
export function useWeather(): WeatherUiState {
const [uiState] = useState<WeatherUiState>({
status: 'success',
city: fakeCities[0],
weather: fakeCurrentWeather[fakeCities[0].name],
});
return uiState;
}Eine Komponente holt den Zustand aus dem Hook, eine zweite zeigt ihn nur an.
export function WeatherScreen() {
const uiState = useWeather();
const [isCelsius, setIsCelsius] = useState(true);
return (
<WeatherContent
uiState={uiState}
isCelsius={isCelsius}
onToggleUnit={() => setIsCelsius(!isCelsius)}
/>
);
}Das Repository liefert die Daten. Der Hook kennt nur den Typ.
import type { City } from '@/model/city';
import type { CurrentWeather } from '@/model/current-weather';
export type WeatherRepository = {
getCurrentWeather(city: City): Promise<CurrentWeather>;
};export function createFakeWeatherRepository({
shouldFail = false,
} = {}): WeatherRepository {
return {
async getCurrentWeather(city) {
await new Promise((resolve) => setTimeout(resolve, 1500));
if (shouldFail) throw new Error('Keine Verbindung (simuliert)');
// ... Fake-Daten zur Stadt zurückgeben
},
};
}useEffect führt Code aus, nachdem die Komponente angezeigt wurde. Damit lädt der Hook die Daten.
useEffect(() => {
let ignore = false;
async function loadWeather() {
const weather = await repository.getCurrentWeather(city);
if (!ignore) setUiState({ status: 'success', city, weather });
// bei Fehler: setUiState({ status: 'error', … })
}
loadWeather();
return () => { ignore = true; }; // Aufräumen
}, [city, repository]);Der Hook bekommt sein Repository als optionalen Parameter. Für die echte API ändert sich später nur eine Zeile.
const defaultRepository = createFakeWeatherRepository();
export function useWeather(
city: City,
repository: WeatherRepository = defaultRepository,
): WeatherUiState {