Wprowadzenie
Podczas budowania aplikacji Animeversum napotkałem znaczące wyzwania związane z zarządzaniem stanem pomiędzy komponentami. Miałem problemy z utrzymaniem stanu dla filtrowanych danych przy użyciu React Context API. Problem był złożony, ponieważ nie pracowałem z bazą danych, lecz z zewnętrznym API z endpointami zwracającymi różne struktury danych.
Stworzyło to sytuację, w której albo pamięć podręczna blokowała nowe wyniki, albo nie mogłem poprawnie zachować danych, ponieważ renderowane wyniki kolidowały z nowymi zapytaniami. Potrzebowałem prostszego rozwiązania i wybrałem Zustand. Początkowo napotkałem pewne trudności, ale ostatecznie udało mi się z powodzeniem zachować wszystkie stany dla wyników, wybranych filtrów i stron, nadal korzystając z pamięci podręcznej serwera bez blokowania jakiejkolwiek funkcjonalności. Dodatkowo mój kod stał się prostszy, czystszy i krótszy.
Czym jest Zustand?
Zustand to lekka biblioteka do zarządzania stanem dla aplikacji React. W przeciwieństwie do bardziej złożonych alternatyw, Zustand oferuje proste podejście do współdzielenia i zachowywania stanu między komponentami. Wymaga minimalnej konfiguracji i bezproblemowo integruje się z TypeScript.
Struktura magazynu (Store)
Aplikacja Animeversum używa centralnego magazynu do zarządzania wszystkimi współdzielonymi stanami:
export const useAnimeStore = create<AnimeFilterState>()(
persist(
(set) => ({
// Stan i akcje
searchQuery: '',
currentPage: 1,
selectedGenres: [],
setSearchQuery: (query) => set({ searchQuery: query }),
resetFilters: () => set(initialState),
// ...więcej stanów i akcji
}),
{
name: 'anime-filters',
partialize: (state) => ({ ...state }),
}
)
);Ten magazyn przechowuje:
- Parametry wyszukiwania (zapytanie, bieżąca strona)
- Wybrane filtry (gatunki, twórcy, studia)
- Preferencje wyświetlania (programy TV, filmy, filtrowanie treści)
- Akcje do aktualizacji tych wartości
Jak działa Zustand
Zustand tworzy magazyn, do którego komponenty mogą uzyskać dostęp poprzez niestandardowy hook. Gdy komponent wywołuje useAnimeStore(), otrzymuje bieżące wartości stanu oraz funkcje do ich aktualizacji:
const {
searchQuery,
currentPage,
selectedGenres,
setCurrentPage,
setSearchQuery
} = useAnimeStore();Gdy stan się zmienia, tylko komponenty używające konkretnych zaktualizowanych wartości będą ponownie renderowane, co zwiększa wydajność aplikacji.
Trwałość stanu
Jedną z kluczowych funkcji Zustand jest wbudowana trwałość. Middleware persist automatycznie zapisuje stan w localStorage:
persist(
(set) => ({ /* implementacja magazynu */ }),
{
name: 'anime-filters',
partialize: (state) => ({ ...state }),
}
)Oznacza to, że gdy użytkownicy opuszczą aplikację i wrócą do niej, ich filtry i parametry wyszukiwania pozostaną nienaruszone.
Implementacja
Komponent "Explore Anime" pokazuje, jak Zustand upraszcza złożone zarządzanie stanem:
Dostęp do wartości magazynu:
const {
searchQuery,
currentPage,
selectedGenres,
// ...inne wartości
} = useAnimeStore();Inicjalizacja z utrwalonego stanu:
useEffect(() => {
if (searchQuery && window.location.hash === '#exploreAnime') {
setCurrentSearchQuery(searchQuery);
setShouldSearch(true);
}
}, []);Budowanie zapytań API ze stanu magazynu:
const params = {
page: page || currentPage,
limit: 25,
sort: scoredBySort || 'desc',
// ...inne parametry
};
if (selectedGenres.length > 0) {
params.genres = selectedGenres.map(g => g.id).join(',');
}Równoważenie stanu lokalnego i globalnego
Komponent "Explore Anime" wykorzystuje zarówno Zustand dla współdzielonego stanu, jak i useState Reacta dla zagadnień specyficznych dla komponentu:
// Stan magazynu (współdzielony między komponentami)
const { searchQuery, currentPage } = useAnimeStore();// Lokalny stan komponentu
const [animeList, setAnimeList] = useState<Anime[]>([]);
const [isSearching, setIsSearching] = useState(false);To podejście utrzymuje współdzielony stan w Zustand, jednocześnie zachowując stan specyficzny dla komponentu lokalnie. Mowa o stanie, który ma znaczenie tylko dla pojedynczego komponentu i nie musi być współdzielony z innymi.
Istnieje kilka ważnych powodów, aby utrzymać ten podział:
- Wydajność - Każda zmiana stanu globalnego potencjalnie wyzwala ponowne renderowanie w wielu komponentach. Utrzymywanie stanu związanego z UI lokalnie minimalizuje niepotrzebne renderowania.
- Prostota - Globalny magazyn pozostaje skoncentrowany na danych, które rzeczywiście muszą być współdzielone, co ułatwia jego zrozumienie i utrzymanie.
- Izolacja - Jeśli komponent ma błąd związany z jego lokalnym stanem, ten błąd jest ograniczony do tego komponentu, nie wpływając na inne części aplikacji.
- Efektywność pamięci - Odłączanie komponentu z lokalnym stanem automatycznie czyści ten stan, podczas gdy stan globalny pozostaje.
Praktyczny przykład
W "Anime Explorer":
- Gdy użytkownik wpisuje tekst w pole wyszukiwania, jest on natychmiast zapisywany w stanie globalnym
(searchQuery), ponieważ inne komponenty mogą potrzebować tej informacji, a powinna ona być zachowana między odsłonami stron. - Gdy wyniki wyszukiwania przychodzą z API, są przechowywane w stanie lokalnym
(animeList), ponieważ:- Są one bezpośrednio wyprowadzane z bieżących filtrów i wyszukiwania
- Żadne inne komponenty nie potrzebują bezpośredniego dostępu do tych wyników
- Jeśli użytkownik przejdzie gdzie indziej i wróci, aplikacja i tak powinna ponownie pobrać świeże wyniki
- Stan wskaźnika ładowania
(isSearching)jest utrzymywany lokalnie, ponieważ tylko ten komponent musi wiedzieć, czy aktualnie pobiera dane.
Synchronizacja filtrów
Gdy filtry się zmieniają, komponent aktualizuje swoje zachowanie:
useEffect(() => {
if (!isInitialSortRender.current) {
if (selectedGenres.length > 0 || selectedCreators.length > 0) {
setCurrentPage(1);
}
setShouldSearch(true);
} else {
isInitialSortRender.current = false;
}
}, [selectedGenres, selectedCreators, selectedStudios]);Zapewnia to, że gdy użytkownicy zmieniają filtry, strona jest resetowana i następuje nowe wyszukiwanie.
Korzyści z Zustand w Animeversum
- Uproszczone zarządzanie stanem: Kod jest czystszy i łatwiejszy w utrzymaniu bez złożonych reduktorów czy typów akcji.
- Automatyczna trwałość: Preferencje użytkownika i filtry pozostają między sesjami bez dodatkowego kodu.
- Optymalizacja wydajności: Komponenty są ponownie renderowane tylko wtedy, gdy zmieniają się ich konkretne wartości stanu.
- Bezpieczeństwo typów: Integracja z TypeScript zapewnia lepsze doświadczenie programisty i mniej błędów.
- Łatwiejsze debugowanie: Zmiany stanu są bardziej przewidywalne i łatwiejsze do śledzenia.
Podsumowanie
Wdrożenie Zustand w aplikacji Animeversum rozwiązało trudny problem trwałości stanu podczas pracy z zewnętrznym API i pamięcią podręczną serwera. Zapewniło prostszą alternatywę dla Context API, umożliwiając czystszy kod i lepsze doświadczenie użytkownika.
Scentralizowany magazyn utrzymuje parametry wyszukiwania, filtry i stan paginacji, zapewniając, że użytkownicy mogą przejść gdzie indziej i wrócić, aby znaleźć swoje wyniki wyszukiwania i filtry nienaruszone. Ten wzorzec działa szczególnie dobrze dla aplikacji, które muszą utrzymywać złożony stan w wielu komponentach, jednocześnie utrzymując implementację prostą i łatwą w utrzymaniu, znacząco poprawiając doświadczenia użytkownika.
Działanie w praktyce możecie sprawidzić pod linkiem:





