Strona główna / Artykuły / Triangulacja aktualizacji do React 19: Jakie nowe API zastępują dostępne rozwiązania tymczasowe

Triangulacja aktualizacji do React 19: Jakie nowe API zastępują dostępne rozwiązania tymczasowe

Praktyczny przegląd zastosowań, Server Actions, useOptimistic oraz React Compiler, wraz z istotnymi zastrzeżeniami i planem dotyczącym pierwszych zmian w React 19, które należy wdrożyć.

1579 słów

React 19 wprowadził większą liczbę nowych interfejsów API niż jakakolwiek inna wersja od lat, a tak duża ilość informacji utrudnia zidentyfikowanie tego, co jest istotne dla istniejącej bazy kodu. Dla większości zespołów kilka nowych funkcji zastępuje już istniejące rozwiązania workaround, jedna zmienia sposób myślenia o ładowaniu danych, a reszta może poczekać. Poniżej przedstawiono te elementy, które są najważniejsze, wraz z przykładami kodu przed i po aktualizacji oraz uwagami, które łatwo przeoczyć, aby można było zaplanować aktualizację w oparciu o jej wartość.

use: odczytywanie Promises i Context podczas renderowania

API use to najważniejsza nowość, a jego zachowanie różni się od wszystkich znanych hooków. Zwykłe hooki muszą być wywoływane na najwyższym poziomie komponentu, w tym samym porządku przy każdym renderowaniu. use jest wyjątkiem od tej zasady: można go wywołać po wczesnym returnie, wewnątrz warunku lub pętli.

W poniższym przykładzie komponent zwraca widok gościa, gdy nie ma userId, a dopiero wtedy odczytuje dane użytkownika:

// React 19 — use() can be called inside conditionals and loops
function UserProfile({ userId }) {
  if (!userId) return <GuestView />;

  // This is valid in React 19
  const user = use(fetchUser(userId));

  return <div>{user.name}</div>;
}

Prawdziwa moc leży w tym, co przyjmuje use: Promise lub Context. Gdy dostarczony jest Promise, React wstrzymuje działanie komponentu do chwili jego rozstrzygnięcia. Poniższe porównanie pokazuje, ile funkcjonalności znika. Starsza wersja śledzi dane oraz flagę ładowania w stanie, pobiera je za pomocą efektu i ręcznie renderuje ikonkę spinera; wersja React 19 odczytuje wartość bezpośrednio:

// Before React 19
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetchUser(userId).then(data => {
      setUser(data);
      setLoading(false);
    });
  }, [userId]);

  if (loading) return <Spinner />;
  return <div>{user.name}</div>;
}

// React 19
function UserProfile({ userId }) {
  const user = use(fetchUser(userId));
  return <div>{user.name}</div>;
}

Praca została przeniesiona, a nie zniknęła: najbliższa granica <Suspense> pokazuje alternatywny widok, dopóki Promise jest w oczekiwaniu, a najbliższa granica błędów obsługuje przypadki odrzucenia. Komponent opisuje jedynie ścieżkę sukcesu.

Promise musi być stabilny

use oczekuje tego samego obiektu Promise przy każdym renderowaniu. Jeśli fetchUser(userId) tworzy nowy obiekt przy każdym renderowaniu, React ponownie wstrzymuje wykonywanie kodu za każdym razem, co powoduje powtarzające się żądania lub komponent, który nigdy nie osiąga stanu gotowości; React ostrzega przed obiektami Promise utworzonymi bez cache’owania podczas renderowania w komponentach klienckich. Dlatego traktuj te przykłady jako ilustracje struktury. Obiekt Promise powinien pochodzić od czegoś, co go cache’uje: biblioteki danych świadomech mechanizmu Suspense, ładowacza frameworku lub komponentu serwerowego, który inicjuje żądanie i przekazuje obiekt Promise jako właściwość. Czasami zaleca się otoczenie tej funkcji wywołania funkcją useMemo, ale React nie gwarantuje, że wartości zapisane w pamięci cache’owej zostaną zachowane, więc nie jest to niezawodne rozwiązanie do przechowywania danych.

Działania serwerowe jako funkcja Reacta

Użytkownicy App Router w Next.js już znają Server Actions. W React 19 stanowią one część samego Reacta (w dokumentacji nazywa się je teraz Server Functions) i są dostępne we wszystkich frameworkach obsługujących Server Components.

Przykład definiuje funkcję asynchroniczną oznaczoną tagiem 'use server', która dodaje użytkownika i ponownie waliduje listę, a następnie przekazuje ją do właściwości action formularza:

// Server Action — runs on the server, called from the client
async function submitForm(formData) {
  'use server';

  const name = formData.get('name');
  await db.users.create({ name });
  revalidatePath('/users');
}

// Client component
function UserForm() {
  return (
    <form action={submitForm}>
      <input name="name" />
      <button type="submit">Add User</button>
    </form>
  );
}

Właściwość action elementu <form> teraz przyjmuje funkcję, w tym taką asynchroniczną; React uruchamia ją podczas wysyłki formularza i przekazuje jej obiekt FormData. Stan oczekiwania, aktualizacje optymistyczne oraz błędy pochodzą z towarzyszących im API: useFormStatus lub useActionState do zarządzania stanem oczekiwania i wyniku, useOptimistic do natychmiastowej informacji zwrotnej, a granice błędów do obsługi niepowodzeń. Ty piszesz logikę zmiany danych; React koordynuje resztę.

Jeden szczegół w tym fragmencie wymaga korekty w rzeczywistym aplikacji. Funkcja z instrukcją 'use server' wewnątrz kodu może być zdefiniowana tylko w komponencie serwerowym. Jeśli UserForm jest komponentem klienckim, przenieś funkcję submitForm do osobnego pliku, na początku którego umieść 'use server', a następnie ją importuj.

O ile komponenty serwerowe usuwają statyczną interfejs użytkownika z pliku klienckiego, o tyle działania serwerowe eliminują ręcznie napisane ścieżki API do wykonywania zmian: mniej kodu, mniej transmisji danych. Mimo to traktuj każde takie działanie jako publiczną ścieżkę i waliduj wprowadzane dane oraz uprawnienia w jego obrębie.

useOptimistic: natychmiastowa informacja zwrotna bez duplikatu stanu

Kiedyś renderowanie wyniku przed potwierdzeniem ze strony serwera oznaczało ręczne zarządzanie dodatkowym stanem. Funkcja useOptimistic bierze aktualny stan oraz funkcję aktualizacji przypominającą reducera i zwraca stan do renderowania wraz z funkcją, która aplikuje zmianę optymistyczną. W tym przypadku dodanie zadania do listy wprowadza tymczasowy element oznaczony jako „w oczekiwaniu”, który jest renderowany w lekko przygaszonym kolorze, dopóki zapis nie zostanie zakończony:

function TodoList({ todos }) {
  const [optimisticTodos, addOptimisticTodo] = useOptimistic(
    todos,
    (currentTodos, newTodo) => [...currentTodos, newTodo]
  );

  async function handleAdd(text) {
    addOptimisticTodo({ id: 'temp', text, pending: true });
    await saveTodo(text); // Server Action
  }

  return (
    <ul>
      {optimisticTodos.map(todo => (
        <li key={todo.id} style={{ opacity: todo.pending ? 0.7 : 1 }}>
          {todo.text}
        </li>
      ))}
    </ul>
  );
}

Gdy wywołujemy addOptimisticTodo, React natychmiast pokazuje listę optymistyczną. Gdy zakończy się powiązana z nią operacja, wartość optymistyczna jest odrzucona, a React ponownie renderuje listę na podstawie właściwości todos, która do tego czasu powinna zawierać zapisany element.

Wcześniej przechowywałeś potwierdzone i w oczekiwaniu na zatwierdzenie kopie danych oraz ręcznie je usuwałeś, gdy występowały różnice; teraz ten proces jest zarządzany przez mechanizm hooków. Istotne są dwie warunki. Po pierwsze, aktualizacja optymistyczna musi odbywać się w ramach jakiejś akcji lub przejścia, na przykład funkcji przekazanej do właściwości action formularza lub zamkniętej w funkcji startTransition; wywołanie jej z zwykłego obsługującego zdarzenia powoduje ostrzeżenie od React, a aktualizacja może się nie wyświetlić. Po drugie, element nadrzędny musi faktycznie otrzymać nowe dane todos po zapisaniu, zwykle poprzez ponowną weryfikację, w przeciwnym razie nowy element zniknie po przywróceniu stanu optymistycznego. Tymczasowe identyfikatory, takie jak 'temp', również powodują kolizje, jeśli szybko dodaje się dwa elementy, dlatego należy generować unikalne identyfikatory. Te przypadki krawędziowe omawiamy w artykule pięć sposobów awarii mechanizmu useOptimistic rollback.

Kompilator React: memoizacja jako standard

Kompilator React, wcześniej znany jako React Forget, pojawił się wraz z React 19 jako opcjonalny krok budowania i ma największy wpływ na codzienny kod. Wstawia on mechanizm memoizacji podczas procesu budowania, dzięki czemu wartości i funkcje są ponownie wykorzystywane, gdy dane wejściowe się nie zmieniają, a elementy potomne unikają ponownego renderowania, gdy ich właściwości są takie same. Ręczne użycie funkcji useMemo, useCallback oraz React.memo staje się w większości przypadków niepotrzebne, jak pokazuje poniższe porównanie:

// Before: manual memoization required
const expensiveValue = useMemo(() => compute(a, b), [a, b]);
const stableCallback = useCallback(() => doSomething(id), [id]);
const MemoizedChild = React.memo(ChildComponent);

// After React Compiler: write normal code
const expensiveValue = compute(a, b);
const handleClick = () => doSomething(id);
// ChildComponent renders only when its props change - automatically

Nadal musisz rozumieć, kiedy i dlaczego komponenty są ponownie renderowane, ale ręczne rozwiązania stają się znacznie mniej istotne. Nasz przegląd wzorców powodujących niepotrzebne ponowne renderowania pozostaje przydatnym materiałem pomocniczym.

Dla nowych projektów kompilator stanowi solidny standard domyślny. W istniejącym kodzie należy go wdrażać ostrożnie – zakłada on, że komponenty są czyste i przestrzegają zasad React, więc kod, który ulega modyfikacji podczas renderowania, może zachowywać się inaczej po skompilowaniu. Włączaj go stopniowo, uruchamiaj testy i najpierw użyj wtyczki ESLint do wykrycia naruszeń. Sprawdź aktualną dokumentację React, aby dowiedzieć się o jego statusie wydania oraz obsługiwanych wersjach React.

Czego przyjąć i w jakiej kolejności

Nagła korzyść, niskie ryzyko

  • useOptimistic tam, gdzie formularze wchodzą w interakcję ze stanem serwera.
  • Server Actions, jeśli używasz Next.js 14 lub nowszej wersji i chcesz zastąpić ścieżki API służące do modyfikacji danych.

Należy to zaplanować

  • use, gdy warstwa danych generuje stabilne, zbuforowane Promises; naturalnie pasuje do mechanizmu Suspense.
  • Użycie kompilatora React w nowych projektach, a najpierw w dobrze przetestowanych częściach istniejącej aplikacji.
  • Dla większości aplikacji nie jest to pilne

    • Zmiany w ref: ref można teraz przekazywać jako zwykłą właściwość komponentom funkcyjnym, więc forwardRef nie jest już potrzebny.
    • Ulepszenia w obszarze kontekstu, które to głównie zmiany dotyczące doświadczenia programisty, a nie nowego zachowania.
    • Wsparcie metadanych dokumentów, które umożliwia komponentom renderowanie tagów <title> i <meta>, które React umieszcza w nagłówku dokumentu.

    Główne wnioski

    • React 19 to ewolucja, a nie radykalna przepracowana wersja. Jego nowe API formalizują wzorce, które zespoły produkcyjne już tworzą ręcznie: optymistyczne aktualizacje, mutacje na serwerze oraz warunkowe odczytywanie danych.
    • use przenosi obsługę ładowania i błędów do Suspense oraz granic błędów, ale działa dobrze tylko z obietnicami (Promises), które są przechowywane w pamięci poza procesem renderowania.
    • Server Actions i useOptimistic doskonale do siebie pasują; pierwszy zajmuje się zapisem danych, a drugi ukrywa opóźnienia w ich przetwarzaniu.
    • Kompilator ustawia memetyzację jako standardową, pod warunkiem że twoje komponenty przestrzegają zasad React.

    Ważne pytanie brzmi nie o to, czy należy dokonać aktualizacji, ale o to, która z tych technik zastąpi rozwiązanie tymczasowe, które obecnie stosujesz. Zacznij od tego.

    Literatura pokrewna

  • Async Forms w React 19 z use, useActionState i useOptimistic — Jak use(), useActionState, useFormStatus i useOptimistic zastępują ręczne ładowanie i flagi błędów w React 19, wraz z pułapkami, które każdy z tych hooków po cichu ukrywa.