Usamos cookies para mejorar tu experiencia en el sitio
CodeWorlds

Concurrent Mode i Suspense

Wyobraź sobie centrum kontroli misji na stacji kosmicznej, gdzie jednocześnie przetwarzane są setki sygnałów z różnych czujników. Niektóre dane są pilne (alarmy), inne mniej (statystyki pogodowe z odległych planet). React Concurrent Mode działa na podobnej zasadzie - pozwala priorytetyzować aktualizacje interfejsu, aby aplikacja pozostała responsywna nawet podczas ciężkich operacji.

Czym jest Concurrent Mode?

Concurrent Mode (tryb współbieżny) to zestaw mechanizmów w React, które pozwalają na przerywanie i wznawianie renderowania. Tradycyjnie React renderował synchronicznie - gdy zaczynał aktualizację, musiał ją dokończyć, zanim mógł zareagować na cokolwiek innego. W trybie współbieżnym React może:

  • Rozpocząć renderowanie aktualizacji
  • Przerwać je, gdy pojawi się pilniejsza aktualizacja
  • Wrócić do przerwanej pracy później

To jak kosmiczny kontroler, który może odłożyć analizę danych z dalekiej sondy, gdy nagle pojawi się alert o zbliżającej się asteroidzie.

startTransition - priorytetyzacja aktualizacji

startTransition
to API, które mówi Reactowi: "ta aktualizacja nie jest pilna, możesz ją przerwać". Jest idealne dla operacji, które mogą chwilę poczekać, jak filtrowanie dużej listy czy nawigacja między zakładkami.

1import { useState, startTransition } from 'react';
2
3function GalaxySearch() {
4  const [searchInput, setSearchInput] = useState('');
5  const [searchResults, setSearchResults] = useState([]);
6
7  const handleSearch = (value) => {
8    // Aktualizacja inputa jest PILNA - użytkownik musi widzieć co pisze
9    setSearchInput(value);
10
11    // Filtrowanie wyników jest MNIEJ PILNE
12    startTransition(() => {
13      const filtered = galaxyDatabase.filter(
14        galaxy => galaxy.name.toLowerCase().includes(value.toLowerCase())
15      );
16      setSearchResults(filtered);
17    });
18  };
19
20  return (
21    <div>
22      <input
23        value={searchInput}
24        onChange={(e) => handleSearch(e.target.value)}
25        placeholder="Szukaj galaktyk..."
26      />
27      <GalaxyList results={searchResults} />
28    </div>
29  );
30}

Dzięki

startTransition
input reaguje natychmiast na wpisywanie, nawet jeśli filtrowanie tysięcy galaktyk zajmuje chwilę.

useTransition - śledzenie stanu przejścia

useTransition
to hook, który oprócz
startTransition
daje nam flagę
isPending
, informującą, czy przejście jest w toku:

1import { useState, useTransition } from 'react';
2
3function MissionDashboard() {
4  const [activeTab, setActiveTab] = useState('overview');
5  const [isPending, startTransition] = useTransition();
6
7  const switchTab = (tab) => {
8    startTransition(() => {
9      setActiveTab(tab);
10    });
11  };
12
13  return (
14    <div>
15      <nav>
16        {['overview', 'crew', 'systems', 'navigation'].map(tab => (
17          <button
18            key={tab}
19            onClick={() => switchTab(tab)}
20            style={{
21              opacity: isPending && activeTab !== tab ? 0.7 : 1,
22              fontWeight: activeTab === tab ? 'bold' : 'normal',
23            }}
24          >
25            {tab.toUpperCase()}
26          </button>
27        ))}
28      </nav>
29
30      {isPending && <div className="loading-bar">Ładowanie panelu...</div>}
31
32      <TabContent tab={activeTab} />
33    </div>
34  );
35}

Suspense - deklaratywne oczekiwanie na dane

Suspense
to komponent, który pozwala "zawiesić" renderowanie, dopóki dane nie będą gotowe. Zamiast ręcznie zarządzać stanami ładowania, deklarujesz co pokazać podczas oczekiwania:

1import { Suspense } from 'react';
2
3function SpaceStation() {
4  return (
5    <div>
6      <h1>Stacja Kosmiczna Alpha</h1>
7
8      <Suspense fallback={<LoadingSpinner message="Ładowanie danych załogi..." />}>
9        <CrewList />
10      </Suspense>
11
12      <Suspense fallback={<LoadingSpinner message="Sprawdzanie systemów..." />}>
13        <SystemsStatus />
14      </Suspense>
15    </div>
16  );
17}

Każda sekcja ładuje się niezależnie - użytkownik widzi dane załogi, gdy tylko będą gotowe, nie czekając na status systemów.

Suspense z lazy loading

Najczęstsze zastosowanie Suspense to ładowanie komponentów dynamicznie za pomocą

React.lazy
:

1import { lazy, Suspense } from 'react';
2
3// Komponenty ładowane dynamicznie
4const StarMap = lazy(() => import('./StarMap'));
5const MissionLog = lazy(() => import('./MissionLog'));
6const CrewManagement = lazy(() => import('./CrewManagement'));
7
8function CommandCenter() {
9  const [view, setView] = useState('map');
10
11  return (
12    <div>
13      <nav>
14        <button onClick={() => setView('map')}>Mapa gwiazd</button>
15        <button onClick={() => setView('log')}>Dziennik misji</button>
16        <button onClick={() => setView('crew')}>Załoga</button>
17      </nav>
18
19      <Suspense fallback={<div>Ładowanie modułu...</div>}>
20        {view === 'map' && <StarMap />}
21        {view === 'log' && <MissionLog />}
22        {view === 'crew' && <CrewManagement />}
23      </Suspense>
24    </div>
25  );
26}

Zagnieżdżone Suspense

Możemy zagnieżdżać komponenty

Suspense
, tworząc hierarchię ładowania. Zewnętrzny Suspense łapie wszystko, wewnętrzne pozwalają na bardziej granularną kontrolę:

1function MissionControl() {
2  return (
3    <Suspense fallback={<FullPageLoader />}>
4      <header>
5        <MissionTitle />
6      </header>
7
8      <main>
9        <Suspense fallback={<SectionLoader text="Mapa..." />}>
10          <InteractiveStarMap />
11        </Suspense>
12
13        <aside>
14          <Suspense fallback={<SectionLoader text="Statystyki..." />}>
15            <MissionStats />
16          </Suspense>
17
18          <Suspense fallback={<SectionLoader text="Komunikaty..." />}>
19            <CommunicationLog />
20          </Suspense>
21        </aside>
22      </main>
23    </Suspense>
24  );
25}

useDeferredValue - opóźnione wartości

useDeferredValue
to hook, który pozwala opóźnić aktualizację wartości. React najpierw wyrenderuje z poprzednią wartością (pilna aktualizacja), a potem z nową (mniej pilna):

1import { useState, useDeferredValue, useMemo } from 'react';
2
3function PlanetExplorer() {
4  const [filter, setFilter] = useState('');
5  const deferredFilter = useDeferredValue(filter);
6
7  // Lista renderuje się z opóźnioną wartością filtra
8  const filteredPlanets = useMemo(() => {
9    return allPlanets.filter(p =>
10      p.name.toLowerCase().includes(deferredFilter.toLowerCase())
11    );
12  }, [deferredFilter]);
13
14  const isStale = filter !== deferredFilter;
15
16  return (
17    <div>
18      <input
19        value={filter}
20        onChange={(e) => setFilter(e.target.value)}
21        placeholder="Filtruj planety..."
22      />
23      <div style={{ opacity: isStale ? 0.6 : 1 }}>
24        {filteredPlanets.map(planet => (
25          <PlanetCard key={planet.id} planet={planet} />
26        ))}
27      </div>
28    </div>
29  );
30}

Kiedy używać poszczególnych API?

| API | Zastosowanie | Przykład | |-----|-------------|---------| |

startTransition
| Aktualizacje, które mogą poczekać | Zmiana filtrów, nawigacja między zakładkami | |
useTransition
| Jak wyżej + wskaźnik ładowania | Przełączanie widoków z loading state | |
Suspense
| Oczekiwanie na dane lub kod | Lazy loading, pobieranie danych | |
useDeferredValue
| Opóźnienie renderowania wartości | Filtrowanie dużych list, wyszukiwanie |

Podsumowanie

Concurrent Mode i Suspense to jak zaawansowany system zarządzania priorytetami na stacji kosmicznej. Zamiast przetwarzać wszystkie sygnały sekwencyjnie (blokując ważne alerty na czas analizy mniej istotnych danych), React może inteligentnie priorytetyzować aktualizacje:

  • startTransition pozwala oznaczyć mniej pilne aktualizacje
  • useTransition dodatkowo informuje o stanie przejścia
  • Suspense deklaratywnie zarządza oczekiwaniem na dane
  • useDeferredValue opóźnia kosztowne renderowania

Te mechanizmy współpracują ze sobą, tworząc responsywne interfejsy nawet w złożonych aplikacjach z dużą ilością danych i ciężkimi operacjami.

Ir a CodeWorlds